Improve your Jest test code with Typescript ADTs
Tags: typescript, javascript, jest, angular
Published: 2019-06-16 19:39:28

---
title: Improve your Jest test code with Typescript ADTs
published: true
description: Typing angular mocks in jest unit tests
tags: typescript, JavaScript, Jest, Angular
cover_image: https://thepracticaldev.s3.amazonaws.com/i/gcsylup57n3n2sa8ckuf.jpg
series: typescript
---

Jest is a [JavaScript testing framework](https://jestjs.io/) that's widely used in JavaScript projects. I personally use it on an Angular project written in TypeScript. It makes it easy to create mocks of services for unit tests, and the tests themselves are easy to read, understand, and extend when necessary.

When a component changes, its testing should also change to check the correctness of the new implementation. It is however possible to alter a component or an injected service without any of the tests failing or giving any warning. Some of these cases and the ways TypeScript types help minimize their occurrence are the topic of this post.

Let's use this `TestService` testing file as an example:

```typescript
describe('TestService', () => {
    let authenticationServiceMock;
    let SUT: TestService;

    beforeEach(() => {
        const authorizationSources = ['system', 'override', 'automation'];

        authenticationServiceMock = {
            getAuthSources: jest.fn(() => authorizationSources),
            isSourceAuthorized: jest.fn((sourceCandidate: string) => authorizationSources.includes(sourceCandidate)),
            login: jest.fn((username: string, password: string) => of(username === 'admin' && password === '123')),
        };

        TestBed.configureTestingModule({
            providers: [
                { provide: AuthenticationService, useValue: authenticationServiceMock },
            ]
        });
        SUT = TestBed.get(TestService);
    });

    test('should be created', () => {
        expect(SUT).toBeTruthy();
    });

    test('can login', () => {
        const user = 'wrong';
        const pass = 'wrongpass';
        SUT.login(user, pass).subscribe(
            result => {
                expect(result).toBe(false);
            }
        );
        expect(authenticationServiceMock.login as jest.Mock).toHaveBeenCalledTimes(1);
        expect((authenticationServiceMock.login as jest.Mock).mock.calls[0][0]).toBe(user);
    });
});
```

Several things here could be improved to avoid spending time changing the test file with minor details each time something is changed in the tested service. It could also be made more type-safe.

* The AuthenticationService mock has no type, so any changes to AuthenticationService would cause this test to continue passing when it shouldn't. It could also fail even though TestService would also change along with its dependency, but then the test would fail, again due to the outdated mock implementation of AuthenticationService
* If we gave AuthenticationService a type, we would still need to cast its functions to `jest.Mock` to use jasmine matchers like `toHaveBeenCalledTimes`, or to access the jest mockInstance `mock` property to check the arguments in a function call.
* When using the `mock.calls` array, it's just a `any[][]` type, and if we wanted to get the actual type of the parameters to the `login` method, we would have to cast it to the explicit and wordy mock type, like so:

  ```typescript
  expect((authenticationServiceMock.login as jest.Mock<Observable<boolean>, [string, string]>).mock.calls[0][0]).toBe(user);
  ```
  
* Even using this syntax, again any changes to `authenticationService` or to `login`'s signature would require us to manually fix all of these casts, and it wouldn't even be that clear that the `jest.Mock` cast is the issue. Imagine `login` used to take `[string, number]` as input and we now refactored it to be `[string, string]`. We would get a very wordy error message, from which it would be hard to tell that we just need to switch the second argument's type to `string`.

The most basic thing we can do, is tell the compiler that our mock is of type `AuthenticationService`, but all of its methods are also of type `jest.Mock`. To do this we first need to extract all of the method names from `AuthenticationService`, and then create a `Record` type where the keys are the method names and the values are all `jest.Mock`:

```typescript
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never }[keyof T];
```

This type alias uses [Mapped Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types) and [Index Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types) to create a type that's a union of property names from the type `T`. In our case `FunctionPropertyNames<AuthenticationService>` means exactly `"login" | "getAuthSources" | "isSourceAuthorized"`. Our mock service type alias will therefore be:

```typescript
type MockService<aService> = aService & Record<FunctionPropertyNames<aService>, jest.Mock>;

let authenticationServiceMock: MockService<AuthenticationService>;
```

Now we can use our mock anywhere the original service would be required (because it has the original service type), but whenever we access one of its properties, if it's a function, it will have the additional type `jest.Mock`. For example:

```typescript
expect(authenticationServiceMock.login).toHaveBeenCalledTimes(1);
expect((authenticationServiceMock.login).mock.calls[0][0]).toBe(user);
```

No more awkward casting whenever we want to `expect` anything!

Do note that the `mock` object still uses the `<any, any>` type signature, because we did not say what the return type and parameters should be for each function. To do that we'll need to map directly over the original service type (again using Mapped Types), so we can tell each function property to be a mock of the proper type:

```typescript
type BetterMockService<aService> = aService &
    { [K in keyof aService]: aService[K] extends (...args: infer A) => infer B ?
        aService[K] & jest.Mock<B, A> : aService[K] };
```

Now we're creating a type that has all of the same properties as `aService`, but for each property that's a function, it has the additional type of `jest.Mock<B, A>`, where `B` is its return type and `A` is a tuple of its parameters' types. We don't need to include `aService` in the intersection, since the new mapped type already has all of the properties of the original, but I've kept it here to show the similarity to the previous solution.

Hopefully this idea or adaptations of it can help you when typing mocks in your unit tests. Let me know of other typing tricks you use.