Bagaimana saya bisa mengejek impor modul ES6 menggunakan Jest?

281

Saya mulai berpikir ini tidak mungkin, tetapi saya tetap ingin bertanya.

Saya ingin menguji bahwa salah satu modul ES6 saya memanggil modul ES6 lain dengan cara tertentu. Dengan Jasmine ini sangat mudah -

Kode aplikasi:

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

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

Dan kode tes:

//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);
  });
});

Apa yang setara dengan Jest? Aku merasa ini hal yang sangat sederhana untuk dilakukan, tapi aku sudah mencabuti rambutku untuk mencari tahu.

Yang paling dekat saya datang adalah dengan mengganti imports dengan requires, dan memindahkannya di dalam tes / fungsi. Tak satu pun dari hal-hal itu yang ingin saya lakukan.

// 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);
  });
});

Untuk poin bonus, saya ingin semuanya berfungsi ketika fungsi di dalamnya dependency.jsadalah ekspor default. Namun, saya tahu bahwa memata-matai ekspor default tidak berfungsi di Jasmine (atau setidaknya saya tidak pernah bisa membuatnya berfungsi), jadi saya tidak mengulurkan harapan bahwa itu mungkin di Jest juga.

Cam Jackson
sumber
Saya menggunakan Babel untuk proyek ini, jadi saya tidak keberatan terus transpile imports ke requires untuk saat ini. terimakasih untuk pemberitahuannya.
Cam Jackson
bagaimana jika saya memiliki ts kelas A dan memanggil beberapa fungsi katakanlah doSomething () dari kelas B bagaimana kita bisa mengejek sehingga kelas A membuat panggilan ke versi mengejek fungsi kelas B doSomething ()
kailash yogeshwar
bagi mereka yang ingin menemukan masalah ini lebih lanjut github.com/facebook/jest/issues/936
omeralper

Jawaban:

221

Saya sudah bisa menyelesaikan ini dengan menggunakan hack yang melibatkan import *. Ia bahkan berfungsi untuk ekspor bernama dan default!

Untuk ekspor bernama:

// 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);
  });
});

Atau untuk ekspor standar:

// 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
  });
});

Seperti yang ditunjukkan Mihai Damian dengan tepat di bawah ini, ini memutasikan objek modul dependency, dan karenanya akan 'bocor' ke pengujian lain. Jadi, jika Anda menggunakan pendekatan ini, Anda harus menyimpan nilai asli dan kemudian mengaturnya kembali setelah setiap tes. Untuk melakukan ini dengan mudah dengan Jest, gunakan metode spyOn () alih-alih jest.fn()karena mendukung dengan mudah mengembalikan nilai aslinya, karena itu hindari sebelum disebutkan 'bocor'.

Cam Jackson
sumber
Terima kasih telah berbagi. Saya pikir hasil bersih mirip dengan ini - tetapi ini mungkin lebih bersih - stackoverflow.com/a/38414160/1882064
arcseldon
64
Ini bekerja, tetapi mungkin itu bukan praktik yang baik. Perubahan pada objek di luar ruang lingkup tes tampaknya bertahan di antara tes. Ini nantinya dapat menyebabkan hasil yang tidak terduga dalam tes lain.
Mihai Damian
10
Alih-alih menggunakan jest.fn (), Anda bisa menggunakan jest.spyOn () sehingga Anda dapat mengembalikan metode asli nanti, sehingga tidak berdarah ke tes lain. Saya menemukan artikel yang bagus tentang pendekatan yang berbeda di sini (jest.fn, jest.mock dan jest.spyOn): medium.com/@rickhanlonii/understanding-jest-mock-f0046c68e53c .
Martinsos
2
Hanya sebuah catatan: jika dependencyberada pada file yang sama dengan myModule, itu tidak akan berfungsi.
Lu Tran
3
Saya pikir ini tidak akan berfungsi dengan mengesampingkan objek yang Anda mutasi hanya-baca
adredx
172

Anda harus mengejek modul dan mengatur sendiri mata-mata:

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);
  });
});
Andreas Köberle
sumber
4
Ini sepertinya tidak benar. Saya mengerti: babel-plugin-jest-hoist: The second argument of jest.mock must be a function.Jadi kodenya bahkan tidak bisa dikompilasi.
Cam Jackson
3
Maaf, saya telah memperbarui kode saya. Harap perhatikan juga bahwa jalur masuk jest.mockrelatif terhadap file uji.
Andreas Köberle
1
Namun, ini berhasil bagi saya, tidak saat menggunakan ekspor default.
Iris Schaffer
4
@IrisSchaffer untuk melakukan pekerjaan ini dengan ekspor default yang Anda perlu tambahkan __esModule: trueke objek tiruan. Itu adalah bendera internal yang digunakan oleh kode yang ditranskrip untuk menentukan apakah itu adalah modul es6 yang ditranskrip atau modul commonjs.
Johannes Lumpe
24
Mengejek ekspor standar: jest.mock('../dependency', () => ({ default: jest.fn() }))
Neob91
50

Untuk mengejek ekspor standar modul ketergantungan ES6 menggunakan lelucon:

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);
  });
});

Pilihan lain tidak berfungsi untuk kasus saya.

falsarella
sumber
6
apa cara terbaik untuk membersihkan ini jika saya hanya ingin melakukan satu tes? di dalam AfterEach? `` `` afterEach (() => {jest.unmock (../ dependency ');}) `` ``
nxmohamad
1
@ Falalsella doMock benar-benar bekerja dalam kasus itu? Saya mengalami masalah yang sangat mirip dan tidak melakukan apa-apa ketika saya mencoba jest.doMock di dalam tes khusus, di mana jest.mock untuk seluruh modul bekerja dengan benar
Progress1ve
1
@ Progress1ve Anda dapat mencoba menggunakan jest.mock dengan mockImplementationOnce juga
falsarella
1
Yup, itu saran yang valid, namun itu membutuhkan tes untuk menjadi yang pertama dan saya bukan penggemar tes menulis sedemikian rupa. Saya mengatasi masalah-masalah itu dengan mengimpor modul eksternal dan menggunakan spyOn pada fungsi tertentu.
Progress1ve
1
@ Progress1ve hmm saya bermaksud menempatkan mockImplementationSetelah di dalam setiap tes tertentu ... bagaimanapun, saya senang Anda menemukan solusi :)
falsarella
38

Menambahkan lebih banyak ke jawaban Andreas. Saya memiliki masalah yang sama dengan kode ES6 tetapi tidak ingin mengubah impor. Itu tampak berantakan. Jadi saya melakukan ini

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

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

Dan menambahkan dependency.js di folder "__ mocks __" sejajar dengan dependency.js. Ini berhasil untuk saya. Juga, ini memberi saya opsi untuk mengembalikan data yang sesuai dari implementasi tiruan. Pastikan Anda memberikan jalur yang benar ke modul yang ingin Anda tiru.

mdsAyubi
sumber
Terima kasih untuk ini. Akan mencobanya. Menyukai solusi ini juga - stackoverflow.com/a/38414160/1882064
arcseldon
Apa yang saya suka dari pendekatan ini adalah memberi Anda kemungkinan untuk memberikan satu tiruan manual untuk semua kesempatan di mana Anda ingin mengejek modul tertentu. Saya misalnya, memiliki pembantu terjemahan, yang digunakan di banyak tempat. The __mocks__/translations.jsberkas hanya bawaan ekspor jest.fn()dalam sesuatu seperti:export default jest.fn((id) => id)
Iris Schaffer
Anda juga dapat menggunakan jest.genMockFromModuleuntuk menghasilkan tiruan dari modul. facebook.github.io/jest/docs/…
Varunkumar Nagarajan
2
Satu hal yang perlu diperhatikan adalah bahwa modul ES6 yang diejek export default jest.genMockFromModule('../dependency')akan memiliki semua fungsi yang ditugaskan dependency.defaultsetelah memanggil `jest.mock ('.. dependency'), tetapi jika tidak berperilaku seperti yang diharapkan.
jhk
7
Seperti apa bentuk pernyataan pengujian Anda? Itu sepertinya bagian penting dari jawabannya. expect(???)
batu
14

Penerusan cepat ke 2020, saya menemukan tautan ini sebagai solusinya. hanya menggunakan sintaks modul 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

Juga satu hal yang perlu Anda ketahui (yang membuat saya perlu waktu untuk mencari tahu) adalah bahwa Anda tidak dapat memanggil jest.mock () di dalam tes; Anda harus menyebutnya di tingkat atas modul. Namun, Anda dapat memanggil mockImplementation () di dalam masing-masing tes jika Anda ingin menyiapkan tiruan yang berbeda untuk tes yang berbeda.

Andy
sumber
5

Pertanyaannya sudah dijawab tetapi Anda dapat menyelesaikannya seperti ini:

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
  });
});
Ramping
sumber
Tapi "wajibkan" adalah sintaks CommonJS - OP bertanya tentang Modul ES6
Andy
@Andy terima kasih atas komentar Anda, saya memperbarui jawaban saya. Hal yang sama BTW dalam logika.
Slim
2

Saya memecahkan ini dengan cara lain. Katakanlah Anda memiliki dependency.js Anda

export const myFunction = () => { }

Saya membuat file depdency.mock.js selain itu dengan konten berikut:

export const mockFunction = jest.fn();

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

dan dalam tes, sebelum saya mengimpor file yang memiliki ketergantungan saya gunakan:

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

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

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
Felipe Leusin
sumber