最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Jest test fails when trying to test an asynchronous function throws - Stack Overflow

programmeradmin2浏览0评论

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;
    

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;
    
Share Improve this question edited Jun 12, 2018 at 13:50 FLC asked Jun 11, 2018 at 23:01 FLCFLC 8952 gold badges7 silver badges18 bronze badges 2
  • Are you always returning a Promise (is getKmlFilename declared async)? – Narigo Commented Jun 12, 2018 at 0:06
  • @Narigo yes, as you can see in my update – FLC Commented Jun 12, 2018 at 13:51
Add a comment  | 

1 Answer 1

Reset to default 15

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.

发布评论

评论列表(0)

  1. 暂无评论