Search code examples
typescriptunit-testingfunctional-programming

Automocking function dependencies for unit testing in typescript


I'm trying to implement a helper function for my unit tests so that I can automatically mock all dependencies of a function in a functional programming typescript way.

Example:

I have a function E.g.

createBreakfast(deps);

Now deps is an interface of type Depsof the createBreakfast function: Eg.

export interface Deps {
scrambleEggs:(numberOfEggs: number, ingredients: Ingredients) => Promise<string>;
makeToast:({wantButter, howManyPieces, personMakingTheToast}:{wantButter:boolean, howManyPieces: number, personMakingTheToast: Person}) => Promise<string>;
}

Now to unit test this, perhaps, I want to mock scrambleEggs and personMakingTheToast and don't care about the return value but I do want to mock the implementation of makeToast

An example would be:

const scrambleEggs = jest.fn();
const makeToast = jest.fn();
const personMakingTheToast = jest.fn();
const deps = {scrambleEggs, makeToast, personMakingTheToast}
makeToast.mockReturnValueOnce("It's ready");
const result = await createBreakfast(deps);

What I'm looking for is a helper function that can automatically mock functions, parameters, anything, if I don't explicitly provide it:

Mock code:

const makeToast = jest.fn();
makeToast.mockReturnValueOnce("It's ready");
const deps= createDeps<createBreakfast>(makeToast);
//Since I didn't pass in scrambleEggs and personMakingTheToast, 
//I'd like createDeps to understand to automatically mock it by inspecting the 
//createBreakfast function, realizing the parameters, and mocking anything not passed in
const result = await createBreakfast(deps);

Could someone help me write code for the createDeps function?

The function should be dynamic as this function could grow much larger in size. I'd like the function to be reused for any function so that I can use it in all my unit tests to test easier.


Solution

  • What you ask for is impossible, at least at run-time.

    TypeScript types/interfaces are only used by the type checker and do not generate any code, so they cannot be accessed at run-time.

    Even if it was possible, first off, you'd have to use the TypeScript typeof operator to hand over a function's signature to a type parameter:

    const deps= createDeps<typeof createBreakfast>(makeToast);
    

    Even then, createDeps would not have access to createBreakfast at run-time, so you would have to hand in the function itself, too. But that still would not allow to do "reflection" on the function signature at run-time.

    The only solution I could imagine would be to use a tool like Babel to generate mock function code from the TypeScript source code of the function to test (here: creteBreakfast) and its dependencies (here: interface Deps). At compile-time, the function signature and all types used by it can be analyzed and corresponding test code can be generated.

    If you plan to go down that road, be aware that Jest itself already uses Babel to transpile code to make it run in Node.js.