Konrad Polek
Konrad Polek

Reputation: 74

Mocking TypeDI service with Jest

I'm using Node with TypeScript, TypeDI and Jest. I'm creating services that depend on each other, let's say:

@Service()
export class MainService{
constructor(private secondService: SecondService){}
public someMethod(someString: string) // implementation depends on secondService
}

@Service()
export class SecondService{
constructor(private thirdService: ThirdService){}
}

@Service()
export class ThirdService{
constructor(){}
}

I want to test MainService, but to instantiate it I need to pass dependency and that dependency needs another dependecy. I tried to do this like, it works, but is ugly:

const secondService = new SecondService(new ThirdService());
jest
    .spyOn(secondService, "someMethod")
    .mockImplementation((someString: string) => {
        // do something
        return something;
    });
const mainService = new MainService(secondService);
// use mainService in tests

Of course creating new instance of dependency is not always an option, and defienetly not an option when it has many dependencies.

I think it should look more like:

const secondService = SomeMockFactory.create(SecondService);

but i can't find any way to create mock while cutting off dependencies. I tried using

const secondService = jest.genMockFromModule("path/to/second/service");

but after trying to spyOn secondService methods TS is throwing error that "someMethod" is not a function. What am I missing / doing wrong? Do I need some other library than Jest?

Upvotes: 2

Views: 5713

Answers (1)

Konrad Polek
Konrad Polek

Reputation: 74

After a while I found out how to do this using default jest behaviour.

First, you need to create mock of SecondService in path/to/second/service/__mocks__, like:

// path/to/second/service/__mocks__/SecondService.ts
const mock = jest.fn().mockImplementation(() => ({
  async thingSecondServiceDoInFirstService(
    param: number
  ): number {
    return 1;

}));
export default mock;

SecondService has to be default export, like:

// path/to/second/service/SecondService.ts
    @Service()
export default class SecondService {
constructor(private thirdService: ThirdService) {}
  async thingSecondServiceDoInFirstService(
    param: number
  ): number {
    return this.thirdService.thingThirdServiceDoInSecond(param);
  }
}

In test file you have to use jest.mock before importing SecondService, and then create SecondService instance from mock:

jest.mock("path/to/second/service/SecondService");
import SecondService from "path/to/second/service/SecondService";
import MainService from "path/to/main/service/MainService";

describe("Test main service", () => {

  const SecondServiceMock = <jest.Mock<SecondService>>SecondService;
  let secondService = new SecondServiceMock();

  beforeEach(() => {
    mainService = new MainService(secondService);
  });

// test logic...
}

As requested, ThirdService is not needed anymore.

Upvotes: 2

Related Questions