How to mock a dependent class without changing constructor parameters to optional in TypeScript & JEST?

672 views Asked by At

I'm trying to imitate something very well-known in mock practice for Java applications, but this time using TypeScript and JEST. Suppose I have a class Controller who depends on a class Service. The Controller declares its dependency through the constructor, making the Service to be mandatory. I use a Dependency Injection (DI) library (tsyringe) to resolve the dependencies in runtime, therefore the DI container will take care of creating an instance of the Service and injecting it into the Controller when the time comes.

For clarity, here it goes the source code for the Controller:

import { scoped, Lifecycle } from "tsyringe";
import { RouteService } from "./RouteService";
import { RouteDTO } from "./view/RouteDTO";

@scoped(Lifecycle.ContainerScoped)
export class RouteController {

    constructor(private routeService: RouteService) {}

    public createRoute(route: RouteDTO): RouteDTO {
        // business logic subject for testing
        if (isBusinessLogicValid) {
            return this.routeService.saveRoute(route);
        } else {
            throw Error("Invalid business logic");
        }
    }
}

and here goes the source code for the Service:

import { scoped, Lifecycle } from "tsyringe";
import { UserSession } from "../user/UserSession";
import { RouteDTO } from "./view/RouteDTO";

@scoped(Lifecycle.ContainerScoped)
export class RouteService {

    constructor(
        private userSession: UserSession
    ) {}

    public saveRoute(route: RouteDTO): RouteDTO {
        // business logic and persistence
        return route
    }

}

I'm trying to mock the class RouteService in a way so that I don't need to create an instance of it manually to unit test the RouteController, otherwise, I'll need to resolve all downstream dependencies (meaning: RouteController depends on RouteService, RouteService depends on UserSession, UserSession depends on ...). In Java using Mockito I'd be able to do something like this:

RouteService routeServiceMock = mock(RouteService.class); // this'd be the goal
// mock definitions on routeServiceMock
RouteController controller = new RouteController(routeServiceMock);
RouteDTO newDTO = createRouteDTO();
RouteDTO savedDTO = controller.save(newDTO);
assertThat(savedDTO).isEqualsTo(newDTO);
//... other assertions

I've been looking at Jest documentation and I couldn't find anything equivalent. Does anybody know if such a thing is feasiable? If yes, how can I do that?

1

There are 1 answers

0
João Pedro Schmitt On BEST ANSWER

It seems I have found a workable solution for a while, but I'm not very happy with the structure of the solution. Basically, I have to double cast it as unknown as RouteService.

I can do the following:

describe('Route controller test', () => {

    beforeEach(() => {
        let routeServiceMock: RouteService = {
            saveRoute: jest.fn(async route => route)
        } as unknown as RouteService
        routeController = new RouteController(routeServiceMock);
    })

   // ...
}