typescriptunit-testingjestjsinquirerjs

How to test a function passed as parameter (validate, filter) to inquirer.prompt in Jest Unit Testing?


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?


Solution

  • 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.