8

I was wondering how to write unit tests for the npm package Inquirer.js, which is a tool to make CLI package more easily. I have read this post but I was't able to make it works.

Here is my code that needs to be tested:

const questions = [
                {
                    type: 'input',
                    name: 'email',
                    message: "What's your email ?",
                },
                {
                    type: 'password',
                    name: 'password',
                    message: 'Enter your password (it will not be saved neither communicate for other purpose than archiving)'
                }
            ];

            inquirer.prompt(questions).then(answers => {
                const user = create_user(answers.email, answers.password);
                let guessing = guess_unix_login(user);
                guessing.then(function (user) {
                    resolve(user);
                }).catch(function (message) {
                    reject(message);
                });
            } );

...and here is the test, written with Mocha:

describe('#create_from_stdin', function () {
            this.timeout(10000);
            check_env(['TEST_EXPECTED_UNIX_LOGIN']);
            it('should find the unix_login user and create a complete profile from stdin, as a good cli program', function (done) {
                const user_expected = {
                    "login": process.env.TEST_LOGIN,
                    "pass_or_auth": process.env.TEST_PASS_OR_AUTH,
                    "unix_login": process.env.TEST_EXPECTED_UNIX_LOGIN
                };
                let factory = new profiler();
                let producing = factory.create();
                producing.then(function (result) {
                    if (JSON.stringify(result) === JSON.stringify(user_expected))
                        done();
                    else
                        done("You have successfully create a user from stdin, but not the one expected by TEST_EXPECTED_UNIX_LOGIN");
                }).catch(function (error) {
                    done(error);
                });
            });
        });

I'd like to fill stdin with the process.env.TEST_LOGIN (to answer the first Inquirer.js question) and process.env.TEST_PASS_OR_AUTH (to answer the second Inquirer.js question) to see if the function create a valid profile (with the value unix_login guessed by the method create of the factory object).

I tried to understand how Inquirer.js unit tests itself, but my understanding of NodeJS isn't good enough. Can you help me with this unit test?

Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56
Oscar
  • 1,071
  • 13
  • 26

2 Answers2

11

You simply mock or stub any functionality that you don't want to test.

  • module.js - simplified example of a module you want to test

    const inquirer = require('inquirer')
    
    module.exports = (questions) => {
      return inquirer.prompt(questions).then(...)
    }
    
  • module.test.js

    const inquirer = require('inquirer')
    const module = require('./module.js')
    
    describe('test user input' () => {
    
      // stub inquirer
      let backup;
      before(() => {
        backup = inquirer.prompt;
        inquirer.prompt = (questions) => Promise.resolve({email: 'test'})
      })
    
      it('should equal test', () => {
        module(...).then(answers => answers.email.should.equal('test'))
      })
    
      // restore
      after(() => {
        inquirer.prompt = backup
      })
    
    })
    

There are libraries to help with mocking/stubbing, like sinon.

Also it was easier to mock inquirer.prompt in this case because .prompt was just a property on the main export inquirer which will refer to the the same object in both module.js and module.test.js. For more complicated scenarios there are libraries that can help, like proxyquire. Or you can create your modules in a way that help you switch out the dependencies easily for testing. For example:

  • module.js - make it a "factory" function which returns your main function with dependencies injected either automatically (via default arguments) or manually.

    module.exports = ({
      inquirer = require('inquirer'),
    } = {}) => (questions) => {
      return inquirer.prompt(questions).then(...)
    }
    
  • module.test.js

    const module = require('./module.js')
    
    describe('test user input' () => {
    
      const inquirer = {prompt: () => Promise.resolve({email: 'test'})};
    
      it('should equal test', () => {
        module({inquirer})(...).then(answers => answers.email.should.equal('test'))
      })
    })
    
laggingreflex
  • 32,948
  • 35
  • 141
  • 196
  • Thank you for the explanation, but perhaps I does not understood all of it. I was hoping it was possible to just fill stdin with some data that inquirer.js will read. Here is how I test normally a standard command line program : `echo "test" | node index.js`. The "index.js" file corresponds to this snippet : https://nodejs.org/dist/latest-v8.x/docs/api/readline.html#readline_readline It produce the following expected output : `OHAI> test Say what? I might have heard 'test' OHAI> Have a great day!` – Oscar Apr 17 '18 at 08:10
  • If you're not mocking libraries you're essentially re-testing the libraries themselves. It might be good for integration testing but your question was about unit testing for which the best practice is to just mock/stub dependencies. – laggingreflex Apr 17 '18 at 10:24
  • Okay, so your advice is to trust inquirer.js and only test my code right ? (but from what I've understood of your code, I need to change my module code and not only my test right ?) – Oscar Apr 17 '18 at 13:10
  • My advice is to mock libraries in unit tests. It's more about isolating what you're unit-testing. As for changes needed in your code, I merely used a simplified example (related to your posted code) to demonstrate how you can do mocking and stubbing. In my first example you wouldn't need to change your module code at all, but I've laid out the caveat that comes with it, and two solutions for it (namely, the library proxyquire, and a "dependency injection" pattern (feel free to do research them respectively and decide what works for you)) – laggingreflex Apr 17 '18 at 15:53
2

Using inquirer.js with jest testing framework

  1. Mock inquirer
  2. Mock .prompt with response

module-test.js

import module from './module';
import inquirer from 'inquirer';

jest.mock('inquirer');

describe('Module test', () => {
  test('user input', async () => {
    expect.assertions(1);
    inquirer.prompt = jest.fn().mockResolvedValue({ email: 'some@example.com' });

    await expect(module()).resolves.toEqual({ email: 'some@example.com' });
  });
});

(Using ES6 or TypeScript syntax.)

Zoltan
  • 4,936
  • 1
  • 35
  • 40