Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest mocking of classes with DI dependencies

Various Jest docs show creation of "automatic" mocks, "manual" mocks, or ES6 class mocks (which instantiate dependencies within the constructor).

But I want to use DI / IOC and inject dependencies into the ctor:

// IBar.ts                                           <--- mock this
export default interface IBar {
  /* ...methods... */
}

// Baz.ts                                            <--- mock this
export default class Baz {
  constructor(spam: Spam, ham: IHam) { /* ... */}
  /* ...other methods... */
}

// Foo.ts                                            <--- test this
export default class Foo {
  constructor(bar: IBar, baz: Baz) { /* ... */}
  /* ...other methods... */
}

So I want to do this in a test:

const barMock = jest.giveMeAMock("../../IBar");  // or jest.giveMeAMock<IBar>();
const bazMock = jest.giveMeAMock("./Baz");       // or jest.giveMeAMock<Baz>();
const foo = new Foo(bar, baz);

expect(foo.something()).toBe(true);

Is this possible with Jest?

(I used some TypeScript syntax above, but it's the same problem for JS/ES6 and TS.)

like image 963
lonix Avatar asked Apr 04 '19 18:04

lonix


People also ask

How do you mock a class object in Jest?

Calling jest.mock() with the module factory parameter​ A module factory is a function that returns the mock. In order to mock a constructor function, the module factory must return a constructor function. In other words, the module factory must be a function that returns a function - a higher-order function (HOF).

Is mocking a dependency injection?

Dependency injection is a way to scale the mocking approach. If a lot of use cases are relying on the interaction you'd like to mock, then it makes sense to invest in dependency injection. Systems that lend themselves easily to dependency injection: An authentication/authorization service.

How do you test a class with Jest?

To test classes with Jest we write assertions for static and instance methods and check if they match expectations. The same process we use when testing functions applies to classes. The key difference is that classes with constructors need to be instantiated into objects before testing.


1 Answers

TypeScript interfaces just get compiled away when the code gets converted to JavaScript...

...but it's definitely possible for a class.

You can auto-mock a module using jest.mock and Jest will keep the API surface of the module the same while replacing the implementation with empty mock functions:

baz.js

export default class Baz {
  doSomething() {
    throw new Error('the actual function throws an error');
  }
}

foo.js

export default class Foo {
  constructor(baz) {
    this.baz = baz;
  }
  doSomething() {
    // ...
    this.baz.doSomething();
    // ...
  }
}

code.test.js

jest.mock('./baz');  // <= auto-mock the module

import Baz from './baz';
import Foo from './foo';

test('Foo', () => {
  const baz = new Baz();  // <= baz is an auto-mocked instance of Baz
  const foo = new Foo(baz);

  foo.doSomething();  // (no error)

  expect(baz.doSomething).toHaveBeenCalled();  // Success!
})
like image 172
Brian Adams Avatar answered Sep 17 '22 00:09

Brian Adams