I've attempted to follow the ES6 Class Mocks page in the Jest documentation to test a method on a TypeScript class Consumer. This class instantiates a Provider object and calls methods on it, so I would like to mock the Provider class.
Directory structure:
.
├── __tests__
│   └── consumers
│       └── Consumer.test.ts
└── js
    ├── providers
    │   └── provider.ts
    └── consumers
        └── Consumer.ts
provider.ts:
export class Provider {
    constructor() {}
    public action(params) {
        // do some stuff that we need to mock
        return something;
    }
}
Consumer.ts:
import {Provider} from "../providers/provider";
export class Consumer {
    private provider: Provider;
    constructor() {
        this.provider = new Provider();
    }
    public doSomething() {
        const result = this.provider.action(params);
        // do something with 'result'
    }
}
My first attempt was with a default "automatic mock":
Consumer.test.ts:
import {Consumer} from "../../js/consumers/Consumer";
jest.mock("../../js/providers/provider");
test("Consumer doSomething", () => {
    // a mock Provider will be instantiated in Consumer's ctor:
    const consumer = new Consumer();
    // however, Provider.action() will return undefined within doSomething()
    consumer.doSomething();
});
This proves that I can replace the real implementation with a mock, but I need to ensure that Provider.action() returns a value, so next I tried:
// at some point we can make this return something, but first check it works
const mockAction = jest.fn();
jest.mock("../../js/providers/provider", () => {
  return jest.fn().mockImplementation(() => {
    return {action: mockAction};
  });
});
test("Consumer doSomething", () => {
    // throws TypeError: provider_1.Provider is not a constructor
    const consumer = new Consumer();
    consumer.doSomething();
});
No matter how I've tried changing the mock, I can't find a solution which allows me to use Consumer as normal from my test. I'd rather avoid creating "manual mocks" so I can keep the codebase cleaner and vary the mock implementation between tests.
You don't have to use a default export here. When using named exports you need to create a mock that matches the "shape" of your module. So in your case:
const mockAction = jest.fn();
jest.mock("../../js/providers/provider", () => ({
 Provider: jest.fn().mockImplementation(() => ({
    action: mockAction
  }))
));
                        I seem to have solved this by ensuring that the dependency to be mocked is a default export:
Provider.ts:
export default class Provider {}
Consumer.ts, Consumer.test.ts:
import Provider from "../providers/provider";
I believe this is because jest.mock() targets a module and provider is a module with a class Provider defined inside it. Without a default export in that module, the exact target for the mock is ambiguous. By making the class a default export, Jest knows to use it as the target for the mock.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With