cotrutatiberiu
cotrutatiberiu

Reputation: 59

Testing js module, Promise.all

How do I test the following javascript module code? My test passes but I can't even cover the Promise.all or any code (trying to do one step at a time), I have tried to follow the advice in How to set a test for multiple fetches with Promise.all using jest

const dbSession = require('server/db/dbSessionModule');
jest.mock('server/db/dbSessionModule');

let controller = {
  post: (request, response) => {
    const dbSession = new dbSessionModule();
    const httpResponse = new HttpResponse();

    Promise.all([bcrypt.hash(request.body.payload.password, config.saltRounds), dbSession.connect()])
      .then(promiseValues => {
        return signupModule.createAccount(dbSession, request.body.payload, promiseValues[0]);
      })
      .then(responsePayload => {
        httpResponse.setSuccess(201, 'ACCOUNT_CREATED', responsePayload);
        response.status(httpResponse.status).json(httpResponse);
      })
      .catch(error => {
        console.log(error);
        httpResponse.setReject(500, 'ACCOUNT_CREATION_ERROR', error);
        response.status(httpResponse.status).json(httpResponse);
      })
      .finally(() => {
        dbSession.release();
      });
  }
};

describe('Signup Controller', function() {
  it('Should hash password and connect user', () => {
    const passowrd = 'test';
    const hashPassword = '3rasf4#4ad$@!';

    const bcrypt = {
      hash: jest.fn().mockImplementation(function() {
        return Promise.resolve(hashPassword);
      })
    };

    return expect(Promise.all([bcrypt.hash(passowrd, 5), dbSession.connect()])).resolves.toEqual([hashPassword, {}]);
  });
});

This is my coverage report:

code coverage report showing 0% statement coverage, for a function involving Promise.all, with large parts highlighted in red to indicate it not being covered.

Upvotes: 4

Views: 2010

Answers (1)

Lin Du
Lin Du

Reputation: 102567

Here is a TypeScript solution.

To convert to JavaScript replace (dbSessionModule as jest.Mocked<DbSessionModule>) with dbSessionModule, (bcrypt as jest.Mocked<typeof bcrypt>) with bcrypt, and (signupModule as jest.Mocked<typeof signupModule>) with signupModule. Replace all references to .ts with .js.

I emulated your imported modules and the directory structure is:

.
├── HttpResponse.ts
├── bcrypt.ts
├── config.ts
├── dbSessionModule.ts
├── index.spec.ts
├── index.ts
└── signupModule.ts

index.ts, the file will be tested

import HttpResponse from './HttpResponse';
import DbSessionModule from './dbSessionModule';
import bcrypt from './bcrypt';
import signupModule from './signupModule';
import config from './config';

const controller = {
  post: (request, response) => {
    const dbSession = new DbSessionModule();
    const httpResponse = new HttpResponse();

    return Promise.all([bcrypt.hash(request.body.payload.password, config.saltRounds), dbSession.connect()])
      .then(promiseValues => {
        return signupModule.createAccount(dbSession, request.body.payload, promiseValues[0]);
      })
      .then(responsePayload => {
        httpResponse.setSuccess(201, 'ACCOUNT_CREATED', responsePayload);
        response.status(httpResponse.status).json(httpResponse);
      })
      .catch(error => {
        console.log(error);
        httpResponse.setReject(500, 'ACCOUNT_CREATION_ERROR', error);
        response.status(httpResponse.status).json(httpResponse);
      })
      .finally(() => {
        dbSession.release();
      });
  }
};

export default controller;

Unit test index.spec.ts:

import '@babel/polyfill';

import controller from './';
import DbSessionModule from './dbSessionModule';
import bcrypt from './bcrypt';
import signupModule from './signupModule';
import config from './config';
import HttpResponse from './HttpResponse';

const request = {
  body: {
    payload: {
      password: ''
    }
  }
};
const response = {
  status: jest.fn().mockReturnThis(),
  json: jest.fn().mockReturnThis()
};

const HttpResponseMocked = {
  setSuccess: jest.fn().mockImplementation(status => {
    HttpResponseMocked.status = status;
  }),
  setReject: jest.fn().mockImplementation(status => {
    HttpResponseMocked.status = status;
  }),
  status: 0
};

jest.mock('./HttpResponse.ts', () => {
  return jest.fn(() => HttpResponseMocked);
});

jest.mock('./bcrypt.ts');

const DbSessionModuleMocked = {
  connect: jest.fn(),
  release: jest.fn()
};

jest.mock('./dbSessionModule.ts', () => {
  return jest.fn(() => DbSessionModuleMocked);
});

jest.mock('./signupModule.ts');

const dbSessionModule = new DbSessionModule();
const httpResponse = new HttpResponse();

describe('controller', () => {
  describe('#post', () => {
    request.body.payload.password = '123';
    config.saltRounds = 'mocked salt';

    beforeEach(() => {
      (dbSessionModule as jest.Mocked<DbSessionModule>).connect.mockReset();
      (dbSessionModule as jest.Mocked<DbSessionModule>).release.mockReset();
    });
    it('create account correctly', async () => {
      (bcrypt as jest.Mocked<typeof bcrypt>).hash.mockResolvedValueOnce('mocked hash');
      (dbSessionModule as jest.Mocked<DbSessionModule>).connect.mockResolvedValueOnce({});
      (signupModule as jest.Mocked<typeof signupModule>).createAccount.mockResolvedValueOnce({ accountId: 1 });

      await controller.post(request, response);

      expect(bcrypt.hash).toBeCalledWith('123', 'mocked salt');
      expect(dbSessionModule.connect).toBeCalledTimes(1);
      expect(signupModule.createAccount).toBeCalledWith(dbSessionModule, { password: '123' }, 'mocked hash');
      expect(httpResponse.setSuccess).toBeCalledWith(201, 'ACCOUNT_CREATED', { accountId: 1 });
      expect(response.status).toBeCalledWith(201);
      expect(response.json).toBeCalledWith(httpResponse);
      expect(dbSessionModule.release).toBeCalledTimes(1);
    });

    it('create account error', async () => {
      (bcrypt as jest.Mocked<typeof bcrypt>).hash.mockResolvedValueOnce('mocked hash');
      (dbSessionModule as jest.Mocked<DbSessionModule>).connect.mockResolvedValueOnce({});
      (signupModule as jest.Mocked<typeof signupModule>).createAccount.mockRejectedValueOnce(new Error('some error'));

      await controller.post(request, response);

      expect(bcrypt.hash).toBeCalledWith('123', 'mocked salt');
      expect(dbSessionModule.connect).toBeCalledTimes(1);
      expect(signupModule.createAccount).toBeCalledWith(dbSessionModule, { password: '123' }, 'mocked hash');
      expect(httpResponse.setReject).toBeCalledWith(500, 'ACCOUNT_CREATION_ERROR', new Error('some error'));
      expect(response.status).toBeCalledWith(500);
      expect(response.json).toBeCalledWith(httpResponse);
      expect(dbSessionModule.release).toBeCalledTimes(1);
    });
  });
});

Unit test result and coverage:

 PASS  src/stackoverflow/56924299/index.spec.ts
  controller
    #post
      ✓ create account correctly (10ms)
      ✓ create account error (8ms)

  console.log src/stackoverflow/56924299/index.ts:595
    Error: some error
        at Object.<anonymous> (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56924299/index.spec.ts:80:94)
        at step (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56924299/index.spec.ts:32:23)
        at Object.next (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56924299/index.spec.ts:13:53)
        at /Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56924299/index.spec.ts:7:71
        at new Promise (<anonymous>)
        at Object.<anonymous>.__awaiter (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56924299/index.spec.ts:3:12)
        at Object.<anonymous> (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56924299/index.spec.ts:77:32)
        at Object.asyncJestTest (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
        at resolve (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>)

-----------------|----------|----------|----------|----------|-------------------|
File             |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files        |    85.19 |      100 |    71.43 |    85.19 |                   |
 bcrypt.ts       |       50 |      100 |        0 |       50 |               2,3 |
 config.ts       |      100 |      100 |      100 |      100 |                   |
 index.ts        |      100 |      100 |      100 |      100 |                   |
 signupModule.ts |       50 |      100 |        0 |       50 |               2,3 |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.414s, estimated 3s

The completed TypeScript demo is here

Test coverage report:

code coverage report showing 100% of statements covered, and no red highlighting

Upvotes: 1

Related Questions