Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Strongly Typed Mock with TypeScript and Jest

Posted on: 2018-03-13

TypeScript strongly typed nature help reducing the number of tests but unit tests will always be needed to test logic. Jest comes for free when using the React ecosystem and the project "create-react-app" which is also available with TypeScript as the transpiler. In this article, we will see a way to mock an interface that got injected into a class in a strongly typed fashion.

Before getting too far into the detail of the implementation, here is a basic interface that a class use in the application and a class that uses that interface by injection to another class. The goal will be to unit test the class that receives the injected interface. We want to abstract the implementation of the concrete injected class and rely on mocking the interface with Jest.

interface IClassToInject { 
  run(): boolean; 
} 

class ClassToInject implements IClassToInject { 
  public run(): boolean { 
    return Math.random() >= 0.5; 
  } 
} 

class ClassToTest { 
  constructor(private injectedClass: IClassToInject) { } 
}

const myClass = new ClassToTest(new ClassToInject());

The idea will be to use jest.Mock which is generic. The problem that we want to have Jest using the same object but not the real type. The idea is to have Jest using the same structure but with the type swapped to use Jest's stub. TypeScript type mapping can be handy in a situation where we want to preserve the structure but change the type. The type, that I called "Mockify" allows doing exactly this transformation for you. Instead of injecting the interface directly, we inject the Mockify interface. Creating a Mockify version force the definition of each property which you can set to jest.fn(). There is a little of boilerplate that could be automated but overall, the idea is to have the strongly typed nature available. If the interface adds a new member, delete, or rename, the transpiler will raise an error in your test! The notification brings extra validation very soon in the development flow.

The next step is to create a variable that will hold the mocked interface where you can set returned value depending on what is tested in each test. Finally, we set the mock object to the class by injecting the mock by the constructor.

type Mockify<T> = { [P in ßkeyof T]: jest.Mock<{}>; }; 
let mockInterfaceToInject: Mockify<IClassToInject> = { run: jest.fn() };

mockInterfaceToInject.run.mockReturnValue(false); const classToTest = new ClassToTest(mockInterfaceToInject); 

In this article, we saw that it's possible to keep an existing interface and to map its structure with new returned values that are from Jest's mocking framework. It allows keeping in sync with the model and flexible by adding testing capability like giving returned value or count how many time a property is called.