Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest test fails when trying to test an asynchronous function throws

I have an asynchronous function and I want to test for both: success and failure. On success the function returns a string, on failure it throws. I'm failing miserably at testing failure. Here's my code:

  • getKmlFileName.test.js

I have disabled by commenting the code that fails and added the results in comments

    'use strict';

    const path = require('path');
    const fs = require('fs');

    const getKmlFilename = require('./getKmlFileName.js');

    const createGoodFolder = () => {
      const folderPath = fs.mkdtempSync('/tmp/test-getKmlFilename-');
      const fileDescriptor = fs.openSync(path.join(folderPath, 'doc.kml'), 'w');
      fs.closeSync(fileDescriptor);
      return folderPath;
    };

    const createEmptyFolder = () => fs.mkdtempSync('/tmp/test-getKmlFilename-');

    describe('/app/lib/getKmlFilename', () => {
      // Success tests
      test('Should return a KML filename', async () => {
        const result = await getKmlFilename(createGoodFolder());
        expect(result).toMatch(/\.kml$/);
      });

      // Failure tests
      test('Should throw if no KML files in folder', () => {
        // Expected one assertion to be called but received zero assertion calls.
        // expect.assertions(1);

        // expect(function).toThrow(undefined)
        // Received value must be a function, but instead "object" was found
        //return getKmlFilename(createEmptyFolder())
        // .catch(e => expect(e).toThrow());

        // expect(string)[.not].toMatch(expected)
        // string value must be a string.
        // Received:
        // object:
        // [Error: No valid KML file in /tmp/test-getKmlFilename-j2XxQ4]

        return getKmlFilename(createEmptyFolder())
          .catch(e => expect(e).toMatch('No valid KML file in'));
      });

      test('Should throw if no KML files in folder - try/catch version',
        async () => {
        // Expected one assertion to be called but received zero assertion calls.
        // expect.assertions(1);

        try {
          const result = await getKmlFilename(createEmptyFolder());
        } catch (e) {
          // Received value must be a function, but instead "object" was found
          // expect(e).toThrow();

          // expect(string)[.not].toMatch(expected)
          // string value must be a string.
          // Received:
          // object:
          // [Error: No valid KML file in /tmp/test-getKmlFilename-3JOUAX]
          expect(e).toMatch('No valid KML file in');
        }
      });

    });

As you can see, nothing works. I believe my tests are almost an exact copy of Promises example for the first failure test and Async/Await example for the last one, however none works.

I believe the difference with the examples from Jest documentation is that they show how to test a function throws and how to test a Promise that rejects. But my promise rejects by throwing.

Checking the function in the node console I get this log:

// import function
> getKml = require('./getKmlFileName.js')
[AsyncFunction: getKmlFilename]
// trying it with a proper folder shows we get a Promise
> getKml('/tmp/con')
Promise {
  <pending>,
  domain: 
   Domain {
     domain: null,
     _events: { error: [Function: debugDomainError] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] } }
// trying it with a failing folder shows it's a rejected promise which throws
> getKml('/tmp/sin')
Promise {
  <pending>,
  domain: 
   Domain {
     domain: null,
     _events: { error: [Function: debugDomainError] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] } }
> (node:10711) UnhandledPromiseRejectionWarning: Error: No valid KML file in /tmp/sin
    at getKmlFilename (/home/flc/soft/learning/2018.06.08,jest/getKmlFileName.js:14:11)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)
(node:10711) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:10711) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

As you can see from the inlined comments, the function is doing what it should, however I don't know how to test this in Jest. Any help would be very much appreciated.

I case the code here looks too convoluted, I prepared a repository which contains my misfortunes learning Jest

UPDATE 2018.06.12:

Somehow my message got scrambled and lost the first part which was the actual code I'm trying to test, my apologies for that, here it is:

  • getKmlFileName.js

    'use strict';
    const globby = require('globby');
    const path = require('path');
    
    const getKmlFilename = async (workDir) => {
      const pattern = path.join(workDir, '**/*.kml');
      const files = await globby(pattern);
      if (files && files.length > 0) {
        // Return first KML found, if there are others (improbable), ignore them
        return path.basename(files[0]);
      } else {
        throw new Error(`No valid KML file in ${workDir}`);
      }
    };
    
    module.exports = getKmlFilename;
    
like image 987
FLC Avatar asked Jun 11 '18 23:06

FLC


People also ask

Do Jest tests run asynchronously?

It's common in JavaScript for code to run asynchronously. When you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test.

How do you resolve a Promise in Jest?

Just return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail. For example, let's say that fetchData , instead of using a callback, returns a promise that is supposed to resolve to the string 'peanut butter' .

How do you test .then in Jest?

Jest waits until the done callback is called before finishing the test. test('the data will be peanut butter', done => { function callback(data) { expect(data). toBe('peanut butter'); done(); } fetchData(callback); }); In the case where done() is never called, the test fails, which is exactly what you want to happen.


1 Answers

In your first test:

return getKmlFilename(createEmptyFolder())
  .catch(e => expect(e).toMatch('No valid KML file in'));

It would not complain if the Promise resolves.

In the second test

try {
  const result = await getKmlFilename(createEmptyFolder());
} catch (e) {
  ...
}

It also would not complain if the Promise resolves as it would not get into the catch block.

To test Promises, ask yourself these questions:

  1. Should the promise succeed (resolve) or fail (reject)?
  2. Is your result or rejection value an Error or a regular object?

In jest, you should be able to do this:

  1. Resolve to regular object: expect(yourThing()).resolves.toMatchSnapshot()
  2. Resolve to error (never seen that): expect(yourThing()).resolves.toThrow(/something/)
  3. Rejects to an error: expect(yourThing()).rejects.toThrow(/something/)
  4. Rejects to a regular object (are you sure you want this?): expect(yourThing()).rejects.toMatchSnapshot()

Be aware that an async function always returns a value (a Promise object), so the "usual" expect(() => yourThing()).toThrow() will not work. You need to wait for the result of the Promise first (by using resolves or rejects) and then testing it.

like image 94
Narigo Avatar answered Oct 23 '22 13:10

Narigo