可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am attempting to mock a class Mailer
using jest and I can't figure out how to do it. The docs don't give many examples of how this works. The process is the I will have a node event password-reset
that is fired and when that event is fired, I want to send an email using Mailer.send(to, subject, body)
. Here is my directory structure:
project_root -- __test__ ---- server ------ services -------- emails ---------- mailer.test.js -- server ---- services ------ emails -------- mailer.js -------- __mocks__ ---------- mailer.js
Here is my mock file __mocks__/mailer.js
:
const Mailer = jest.genMockFromModule('Mailer'); function send(to, subject, body) { return { to, subject, body }; } module.exports = Mailer;
and my mailer.test.js
const EventEmitter = require('events'); const Mailer = jest.mock('../../../../server/services/emails/mailer'); test('sends an email when the password-reset event is fired', () => { const send = Mailer.send(); const event = new EventEmitter(); event.emit('password-reset'); expect(send).toHaveBeenCalled(); });
and finally my mailer.js
class:
class Mailer { constructor() { this.mailgun = require('mailgun-js')({ apiKey: process.env.MAILGUN_API_KEY, domain: process.env.MAILGUN_DOMAIN, }); } send(to, subject, body) { return new Promise((reject, resolve) => { this.mailgun.messages().send({ from: 'Securely App ', to, subject: subject, html: body, }, (error, body) => { if (error) { return reject(error); } return resolve('The email was sent successfully!'); }); }); } } module.exports = new Mailer();
So, how do I successfully mock and test this class, using Jest? Many thanks for helping!
回答1:
You don't have to mock your mailer class but the mailgun-js
module. So mailgun is a function that returns the function messages
that return the function send
. So the mock will look like this.
for the happy path
const happyPath = () => ({ messages: () => ({ send: (args, callback) => callback() }) })
for the error case
const errorCase = () => ({ messages: () => ({ send: (args, callback) => callback('someError') }) })
as you have this 2 cases it make sense to mock the module inside your test. First you have to mock it with a simple spy where we later can set the implementation for our cases and then we have to import the module.
jest.mock('mailgun-js', jest.fn()) import mailgun from 'mailgun-js' import Mailer from '../../../../server/services/emails/mailer'
As your module uses promises we have 2 options either return the promise from the test or use async/await
. I use the later one for more info have a look here.
test('test the happy path', async() => { //mock the mailgun so it returns our happy path mock mailgun.mockImplementation(() => happyPath) //we need to use async/awit here to let jest recognize the promise const send = await Mailer.send(); expect(send).toBe('The email was sent successfully!') });
If you would like to test that the mailgun send
method was called with the correct parameter you need to adapt the mock like this:
const send = jest.fn((args, callback) => callback()) const happyPath = () => ({ messages: () => ({ send: send }) })
Now you could check that the first parameter for send was correct:
expect(send.mock.calls[0][0]).toMatchSnapshot()
回答2:
Just for Googlers and future visitors, here's how I've setup jest mocking for ES6 classes. I also have a working example at github, with babel-jest for transpiling the ES module syntax so that jest can mock them properly.
__mocks__/MockedClass.js
const stub = { someMethod: jest.fn(), someAttribute: true } module.exports = () => stub;
Your code can call this with new, and in your tests you can call the function and overwrite any default implementation.
example.spec.js
const mockedClass = require("path/to/MockedClass")(); const AnotherClass = require("path/to/AnotherClass"); let anotherClass; jest.mock("path/to/MockedClass"); describe("AnotherClass", () => { beforeEach(() => { mockedClass.someMethod.mockImplementation(() => { return { "foo": "bar" }; }); anotherClass = new AnotherClass(); }); describe("on init", () => { beforeEach(() => { anotherClass.init(); }); it("uses a mock", () => { expect(mockedClass.someMethod.toHaveBeenCalled(); expect(anotherClass.settings) .toEqual(expect.objectContaining({ "foo": "bar" })); }); }); });