0

I'm trying to mock method inquirer.prompt in a unit testing, which has several questions (in the example I put only two examples).

return prompt([
{
    type: 'input',
    name: 'name1',
    message: 'Question 1?',
    filter: (fieldValue: string) => fieldValue.trim(),
    validate: (fieldValue: string) =>   showValidationResults(fieldValue, validateFieldIsNotEmpty),
},
{
    type: 'input',
    name: 'name2',
    message: 'Question 2?',
    validate: (fieldValue: string) =>   showValidationResults(fieldValue, validateFieldIsNotEmpty),
},
...
]);
    

I need to reach a certain unit test coverage so I want to check that the filter and validate functions are being correctly called.

I am only finding solutions that suggest to extract those functions to their own method and then call them directly from the unit test, but this is not a good solution for me because the unit test doesn't still know if the lines of filter and validate are being called, makes the code less clean.

Also, I would prefer not to call private methods directly from the unit test instead of calling the main method of the class.

Is there a way that I could mock prompt so I can inject the answers to the questions and then check that the filter and validate portions are executed?

mardo
  • 581
  • 1
  • 7
  • 11
  • If you mocked prompt such that those functions were called, you'd be re-implementing Inquirer in your test double. At the unit level you could extract the array of options and check the values (including the behaviour of the functions) are correct, otherwise you should test at the _integration_ level that the CLI behaves correctly. – jonrsharpe Dec 21 '22 at 17:31
  • Thanks @jonrsharpe. I definitively don't want to test at integration level since I have already those tests separated. I didn't think in checking the behavior of the function in the array of options, but I would try it. – mardo Dec 22 '22 at 10:59

1 Answers1

-1

Inspired by the comment of @jonrsharpe I came out with the following solution:


const mockValidateFieldIsNotEmpty = jest.fn();
const mockPrompt = jest.fn();

import ExecutionEnvironmentContext from '../../../../ExecutionEnvironmentContext';

import ClassToTest from '../ClassToTest';

jest.mock('../../../../validation/InteractiveAnswersValidator', () => {
    const ActualInteractiveAnswersValidator = jest.requireActual('../../../../validation/InteractiveAnswersValidator');
    return {
        ...ActualInteractiveAnswersValidator,
        validateFieldHasNoSpaces: mockValidateFieldHasNoSpaces,
    };
});

jest.mock('inquirer', () => {
    return {
        Separator: jest.fn(),
        prompt: mockPrompt,
    };
});

describe('Class to test', () => {
    it('should call filter and validate methods of inquirer prompt', async () => {
        const expectedErrorMessages = ['errorMessage'];
        mockValidateFieldIsNotEmpty.mockReturnValue({ result: true });

        mockPrompt.mockImplementation((opt: { type: string; name: string; message: string; filter?: any; validate?: any }[]) => {
            const choice = opt[0];
            expect(opt[0]).toHaveProperty('filter');
            expect(opt[0]).toHaveProperty('validate');
            expect(opt[1]).toHaveProperty('validate');
            expect(opt[0].filter('  mockMessage  ')).toEqual('mockMessage');
            expect(opt[0].validate('  mockMessage  ')).toEqual(true);
            expect(opt[1].validate('mockMessage')).toEqual(true);
        });

        await ClassToTest.callToFunction({ });
    });
});

This way, I'm calling the methods registered inside filter, checking their results. It may not give the biggest testing value but at least I'm reaching 100% coverage in these methods.

mardo
  • 581
  • 1
  • 7
  • 11