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.)
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).
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.
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.
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!
})
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