ฉันจะจำลองการนำเข้าโมดูล ES6 โดยใช้ Jest ได้อย่างไร


281

ฉันเริ่มคิดว่ามันเป็นไปไม่ได้ แต่ฉันต้องการถามต่อไป

ฉันต้องการทดสอบว่าหนึ่งในโมดูล ES6 ของฉันเรียกโมดูล ES6 อื่นด้วยวิธีเฉพาะ ด้วยจัสมินนี่เป็นเรื่องง่ายสุด ๆ -

รหัสแอพ:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

และรหัสทดสอบ:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

อะไรที่เทียบเท่ากับ Jest? ฉันรู้สึกว่านี่เป็นสิ่งง่าย ๆ ที่อยากทำ แต่ฉันก็พยายามจะคิดออก

สิ่งที่ใกล้เคียงที่สุดของฉันคือการแทนที่imports ด้วยrequires และย้ายไปไว้ในการทดสอบ / ฟังก์ชั่น ไม่ใช่สิ่งที่ฉันต้องการจะทำ

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

สำหรับคะแนนโบนัสฉันชอบที่จะทำให้ทุกอย่างทำงานเมื่อฟังก์ชั่นด้านในdependency.jsเป็นการส่งออกเริ่มต้น อย่างไรก็ตามฉันรู้ว่าการสอดแนมในการส่งออกเริ่มต้นไม่ได้ทำงานในจัสมิน (หรืออย่างน้อยฉันก็ไม่สามารถทำงานได้) ดังนั้นฉันจึงไม่ได้หวังว่ามันจะเป็นไปได้ในเจสต์เช่นกัน


ฉันใช้ Babel สำหรับโครงการนี้อยู่แล้วดังนั้นฉันไม่รังเกียจที่จะ transpile imports เป็น s ต่อไปrequireในตอนนี้ ขอบคุณสำหรับหัวขึ้นแม้ว่า
Cam Jackson

เกิดอะไรขึ้นถ้าฉันมีคลาส TS และเรียกใช้ฟังก์ชันบางอย่างสมมติว่า doSomething () ของคลาส B ว่าเราจะเยาะเย้ยได้อย่างไรเพื่อให้คลาส A เรียกใช้ฟังก์ชันคลาสคลาสที่เยาะเย้ย doSomething ()
kailash yogeshwar

สำหรับผู้ที่ต้องการค้นพบปัญหานี้มากขึ้นgithub.com/facebook/jest/issues/936
omeralper

คำตอบ:


221

import *ฉันได้รับสามารถที่จะแก้ปัญหานี้โดยใช้สับที่เกี่ยวข้องกับ มันยังทำงานได้ทั้งการส่งออกที่ตั้งชื่อและเริ่มต้น!

สำหรับการส่งออกที่มีชื่อ:

// dependency.js
export const doSomething = (y) => console.log(y)

// myModule.js
import { doSomething } from './dependency';

export default (x) => {
  doSomething(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mutate the named export

    myModule(2);

    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

หรือสำหรับการส่งออกเริ่มต้น:

// dependency.js
export default (y) => console.log(y)

// myModule.js
import dependency from './dependency'; // Note lack of curlies

export default (x) => {
  dependency(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mutate the default export

    myModule(2);

    expect(dependency.default).toBeCalledWith(4); // Assert against the default
  });
});

เนื่องจาก Mihai Damian ชี้ไปที่ด้านล่างอย่างถูกต้องสิ่งนี้กำลังกลายพันธุ์วัตถุของโมดูลdependencyดังนั้นมันจะ 'รั่วไหล' ไปยังการทดสอบอื่น ๆ ดังนั้นหากคุณใช้วิธีนี้คุณควรเก็บค่าดั้งเดิมไว้แล้วตั้งค่ากลับมาอีกครั้งหลังจากการทดสอบแต่ละครั้ง ในการทำเช่นนี้ได้อย่างง่ายดายด้วย Jest ให้ใช้วิธีspyOn ()แทนjest.fn()เพราะมันรองรับการเรียกคืนค่าดั้งเดิมของมันได้อย่างง่ายดายดังนั้นจึงควรหลีกเลี่ยง 'leaking' ก่อนที่จะกล่าวถึง


ขอบคุณสำหรับการแบ่งปัน. ฉันคิดว่าผลลัพธ์สุทธิคล้ายกับนี้ - แต่สิ่งนี้อาจจะสะอาดกว่า - stackoverflow.com/a/38414160/1882064
arcseldon

64
ใช้งานได้ แต่อาจไม่ใช่วิธีปฏิบัติที่ดี การเปลี่ยนแปลงวัตถุนอกขอบเขตของการทดสอบดูเหมือนจะคงอยู่ระหว่างการทดสอบ สิ่งนี้สามารถนำไปสู่ผลลัพธ์ที่ไม่คาดคิดในการทดสอบอื่น ๆ ในภายหลัง
Mihai Damian

10
แทนที่จะใช้ jest.fn () คุณสามารถใช้ jest.spyOn () เพื่อให้คุณสามารถกู้คืนวิธีการเดิมได้ในภายหลังดังนั้นจึงไม่ได้เป็นการทดสอบอื่น ๆ ผมพบว่าบทความที่ดีเกี่ยวกับวิธีการที่แตกต่างกันที่นี่ (jest.fn, jest.mock และ jest.spyOn): medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
Martinsos

2
เพียงข้อสังเกต: หากไฟล์dependencyนั้นอยู่ในไฟล์เดียวกับmyModuleมันจะไม่ทำงาน
Lu Tran

2
ฉันคิดว่าสิ่งนี้จะไม่ทำงานกับ Typescript วัตถุที่คุณกำลังกลายพันธุ์นั้นเป็นแบบอ่านอย่างเดียว
adredx

172

คุณต้องจำลองโมดูลและตั้งสายลับด้วยตัวเอง:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

4
ดูเหมือนจะไม่ถูกต้อง ฉันได้รับ: babel-plugin-jest-hoist: The second argument of jest.mock must be a function.ดังนั้นรหัสก็ไม่ได้รวบรวม
Cam Jackson

3
ขออภัยฉันอัปเดตรหัสของฉันแล้ว โปรดทราบว่าเส้นทางในjest.mockนั้นสัมพันธ์กับไฟล์ทดสอบ
Andreas Köberle

1
สิ่งนี้ใช้ได้สำหรับฉัน แต่ไม่ใช่เมื่อใช้การส่งออกเริ่มต้น
Iris Schaffer

4
@IrisSchaffer เพื่อให้งานนี้มีการส่งออกเริ่มต้นคุณต้องเพิ่ม__esModule: trueวัตถุจำลอง นั่นคือการตั้งค่าสถานะภายในที่ใช้โดยรหัส transpiled เพื่อตรวจสอบว่าเป็นโมดูล es6 transpiled หรือโมดูล commonjs
Johannes Lumpe

24
เยาะเย้ยการส่งออกเริ่มต้น: jest.mock('../dependency', () => ({ default: jest.fn() }))
Neob91

50

หากต้องการเยาะเย้ยการเอ็กซ์พอร์ตโมดูลโมดูลพึ่งพา ES6 โดยใช้ jest:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

ตัวเลือกอื่น ๆ ใช้ไม่ได้กับกรณีของฉัน


6
อะไรคือวิธีที่ดีที่สุดในการล้างข้อมูลนี้หากฉันต้องการทดสอบเพียงครั้งเดียว ข้างในแต่ละหลัง? `` `` afterEach (() => {jest.unmock (../ การพึ่งพา ');}) `` ``
nxmohamad

1
@falsarella doMock ใช้งานได้จริงหรือไม่ในกรณีนี้ ฉันมีปัญหาที่คล้ายกันมากและไม่ทำอะไรเลยเมื่อฉันพยายามที่จะ jest.doMock ในการทดสอบที่เฉพาะเจาะจงที่ jest.mock สำหรับโมดูลทั้งหมดทำงานอย่างถูกต้อง
Progress1ve

1
@ Progress1ve คุณสามารถลองใช้ jest.mock กับ mockImplementationOnce ได้เช่นกัน
falsarella

1
ใช่นั่นเป็นข้อเสนอแนะที่ถูกต้อง แต่ต้องใช้การทดสอบเพื่อเป็นคนแรกและฉันไม่ได้เป็นแฟนของการเขียนแบบทดสอบในลักษณะนี้ ฉันได้รับปัญหาเหล่านั้นโดยการนำเข้าโมดูลภายนอกและใช้ spyOn กับฟังก์ชั่นเฉพาะ
Progress1ve

1
@ Progress1ve อืมฉันหมายถึงการวาง mockImplementation เมื่ออยู่ในการทดสอบแต่ละครั้ง ... อย่างไรก็ตามฉันมีความสุขที่คุณได้พบวิธีแก้ปัญหา :)
falsarella

38

กำลังเพิ่มคำตอบ Andreas เพิ่มเติม ฉันมีปัญหาเดียวกันกับรหัส ES6 แต่ไม่ต้องการที่จะกลายพันธุ์การนำเข้า นั่นดูแฮ็ค ดังนั้นฉันจึงทำสิ่งนี้

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

และเพิ่ม dependency.js ในโฟลเดอร์ "__ mocks __" ขนานกับ dependency.js สิ่งนี้ใช้ได้สำหรับฉัน นอกจากนี้ยังให้ฉันเลือกที่จะส่งคืนข้อมูลที่เหมาะสมจากการใช้จำลอง ตรวจสอบให้แน่ใจว่าคุณกำหนดเส้นทางที่ถูกต้องให้กับโมดูลที่คุณต้องการจำลอง


ขอบคุณสำหรับสิ่งนี้. จะลองดู ชอบโซลูชันนี้เช่นกัน - stackoverflow.com/a/38414160/1882064
arcseldon

สิ่งที่ฉันชอบเกี่ยวกับวิธีนี้คือมันช่วยให้คุณมีความเป็นไปได้ที่จะให้การจำลองด้วยตนเองหนึ่งครั้งสำหรับทุกโอกาสที่คุณต้องการจำลองโมดูลเฉพาะ ตัวอย่างเช่นฉันมีผู้ช่วยแปลซึ่งใช้ในหลาย ๆ ที่ __mocks__/translations.jsไฟล์เพียงแค่เริ่มต้นการส่งออกjest.fn()ในสิ่งที่ชอบ:export default jest.fn((id) => id)
ไอริส Schaffer

คุณยังสามารถใช้jest.genMockFromModuleเพื่อสร้าง mocks จากโมดูล facebook.github.io/jest/docs/…
Varunkumar Nagarajan

2
สิ่งหนึ่งที่ควรทราบก็คือโมดูล ES6 ที่เยาะเย้ยผ่านexport default jest.genMockFromModule('../dependency')จะมีฟังก์ชั่นทั้งหมดของพวกเขาที่มอบหมายให้dependency.defaultหลังจากเรียก 'jest.mock (' .. dependency ') แต่ก็ทำหน้าที่อย่างที่คาดไว้
jhk

7
การยืนยันการทดสอบของคุณเป็นอย่างไร นั่นดูเหมือนจะเป็นส่วนสำคัญของคำตอบ expect(???)
ศิลา

14

การส่งต่ออย่างรวดเร็วถึงปี 2020 ฉันพบว่าลิงก์นี้เป็นทางออก ใช้ไวยากรณ์โมดูล ES6 เท่านั้นhttps://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

อีกสิ่งหนึ่งที่คุณต้องรู้ (ซึ่งใช้เวลาซักพักหนึ่ง) คือคุณไม่สามารถโทร jest.mock () ภายในการทดสอบ คุณต้องเรียกว่าระดับสูงสุดของโมดูล อย่างไรก็ตามคุณสามารถเรียก mockImplementation () ภายในการทดสอบแต่ละรายการหากคุณต้องการตั้งค่า mocks ที่แตกต่างกันสำหรับการทดสอบที่แตกต่างกัน


5

คำถามได้รับคำตอบแล้ว แต่คุณสามารถแก้ไขได้ดังนี้:

dependency.js

const doSomething = (x) => x
export default doSomething;

myModule.js:

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);

myModule.spec.js:

jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    doSomething.mockImplementation((x) => x * 10)

    myModule(2);

    expect(doSomething).toHaveBeenCalledWith(4);
    console.log(myModule(2)) // 40
  });
});

แต่ "ต้องการ" เป็นไวยากรณ์ CommonJS - OP ถามเกี่ยวกับโมดูล ES6
Andy

@ และขอบคุณสำหรับความคิดเห็นของคุณฉันได้อัพเดตคำตอบแล้ว BTW สิ่งเดียวกันในตรรกะ
ผอม

2

ฉันแก้ไขมันด้วยวิธีอื่น สมมติว่าคุณมีการพึ่งพาของคุณ

export const myFunction = () => { }

ฉันสร้างไฟล์ depdency.mock.js นอกเหนือจากนั้นด้วยเนื้อหาต่อไปนี้:

export const mockFunction = jest.fn();

jest.mock('dependency.js', () => ({ myFunction: mockFunction }));

และในการทดสอบก่อนที่ฉันจะนำเข้าไฟล์ที่มีการพึ่งพาฉันใช้:

import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'

it('my test', () => {
    mockFunction.returnValue(false);

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.