admin管理员组

文章数量:1355925

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.

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.

Share Improve this question asked Mar 11, 2019 at 9:47 EllisEllis 3,0882 gold badges20 silver badges29 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 2

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.

Per the Jest documentation on Manual Mocks => Using With ES Module Imports, Jest sometimes requires to do work before the import. Under normal circumstances, it can "hoist" the mock before the import, even though in your code it appears after the import. However, with Typescript & ECMAScript support, it cannot do this. So for dynamic classes you would have to put your mock before the import.

But there are even more quirky things: if you are trying to mock a static method of your class, then that mock must be placed after the import.

So you end up with code like this:

... Dynamic class mock
... import statement for the dynamic class
... Mocks for static methods

Note that by import I also mean the import inside your consumer class .. i.e. your consumer class in your test file must be imported after the dynamic class mock but before any mocks for static methods.

You may be able to solve all this without resorting to placement by setting esModuleInterop Typescript piler option to true. However, this may have all sorts of side effects on code that is not patible and may be the more difficult route.

本文标签: javascriptUnable to use jestmock for TypeScript classStack Overflow