Come posso prendere in giro un'importazione di un modulo ES6 usando Jest?


281

Sto iniziando a pensare che questo non sia possibile, ma voglio chiederlo comunque.

Voglio testare che uno dei miei moduli ES6 chiama un altro modulo ES6 in un modo particolare. Con Jasmine è semplicissimo -

Il codice dell'app:

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

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

E il codice di prova:

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

Qual è l'equivalente con Jest? Sento che questa è una cosa così semplice da voler fare, ma mi sono strappato i capelli cercando di capirlo.

Il più vicino a cui sono arrivato è sostituendo la imports con requires e spostandola all'interno dei test / funzioni. Nessuna delle due cose che voglio fare.

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

Per i punti bonus, mi piacerebbe far funzionare tutto quando la funzione interna dependency.jsè un'esportazione predefinita. Tuttavia, so che lo spionaggio delle esportazioni predefinite non funziona in Jasmine (o almeno non potrei mai farlo funzionare), quindi non sto sperando che sia possibile anche in Jest.


Sto usando Babel per questo progetto, quindi non mi dispiace continuare a compilare da imports a requires per ora. Grazie per l'attenzione.
Cam Jackson

cosa succede se ho ts classe A e chiama qualche funzione diciamo doSomething () di classe B come possiamo deridere in modo che la classe A faccia chiamata alla versione derisa della funzione di classe B doSomething ()
kailash yogeshwar

per coloro che vogliono scoprire di più questo problema github.com/facebook/jest/issues/936
omeralper

Risposte:


221

Sono stato in grado di risolverlo utilizzando un hack che coinvolge import *. Funziona anche con esportazioni con nome e predefinite!

Per un'esportazione denominata:

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

O per un'esportazione predefinita:

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

Come Mihai Damian ha giustamente sottolineato di seguito, questo sta mutando l'oggetto del modulo dependency, e quindi "passerà" ad altri test. Pertanto, se si utilizza questo approccio, è necessario memorizzare il valore originale e quindi ripristinarlo nuovamente dopo ogni test. Per farlo facilmente con Jest, usa il metodo spyOn () invece che jest.fn()perché supporta facilmente il ripristino del suo valore originale, evitando quindi prima di citare "perdite".


Grazie per la condivisione. Credo che il risultato netto è simile a questa -, ma questo potrebbe essere più pulito - stackoverflow.com/a/38414160/1882064
arcseldon

64
Funziona, ma probabilmente non è una buona pratica. Le modifiche agli oggetti al di fuori dell'ambito del test sembrano persistere tra i test. Ciò può in seguito portare a risultati imprevisti in altri test.
Mihai Damian,

10
Invece di utilizzare jest.fn (), è possibile utilizzare jest.spyOn () in modo da poter ripristinare il metodo originale in un secondo momento, in modo che non venga scaricato in altri test. Ho trovato un bell'articolo sui diversi approcci qui (jest.fn, jest.mock e jest.spyOn): medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c .
Martinsos,

2
Solo una nota: se dependencyrisiede nello stesso file di myModule, non funzionerà.
Lu Tran,

3
Penso che questo non funzionerà con Typescript, l'oggetto che stai mutando è di sola lettura.
adredx,

172

Devi prendere in giro il modulo e impostare la spia da solo:

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
Questo non sembra giusto. Ottengo: babel-plugin-jest-hoist: The second argument of jest.mock must be a function.Quindi il codice non si sta nemmeno compilando.
Cam Jackson

3
Mi dispiace, ho aggiornato il mio codice. Si noti inoltre che il percorso in jest.mockè relativo al file di test.
Andreas Köberle l'

1
Questo ha funzionato per me, tuttavia, non quando si utilizzano le esportazioni predefinite.
Iris Schaffer,

4
@IrisSchaffer per avere questo lavoro con l'esportazione predefinita è necessario aggiungere __esModule: truel'oggetto finto. Questo è il flag interno utilizzato dal codice transpiled per determinare se si tratta di un modulo es6 transpiled o di un modulo commonjs.
Johannes Lumpe,

24
jest.mock('../dependency', () => ({ default: jest.fn() }))
Deridere sulle

50

Per deridere un'esportazione predefinita del modulo di dipendenza ES6 usando 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);
  });
});

Le altre opzioni non hanno funzionato per il mio caso.


6
qual è il modo migliore per ripulirlo se voglio solo fare un test? dentro dopo ogni? `` `` afterEach (() => {jest.unmock (../ dependency ');}) `` ``
nxmohamad,

1
@falsarella funziona davvero in quel caso? Sto riscontrando un problema molto simile e non fa nulla quando sto provando a jest.doMock all'interno di un test specifico, in cui jest.mock per l'intero modulo funziona correttamente
Progress1ve

1
@ Progress1ve puoi provare a usare jest.mock con mockImplementationOnce
falsarella

1
Sì, questo è un suggerimento valido, tuttavia ciò richiede che il test sia il primo e non sono un fan di scrivere test in questo modo. Ho risolto questi problemi importando il modulo esterno e usando spyOn su funzioni specifiche.
Progresso1,

1
@ Progress1ve hmm, intendevo mettere il mockImplementationOnce all'interno di ogni test specifico ... comunque, sono felice che tu abbia trovato una soluzione :)
falsarella

38

Aggiungendo altro alla risposta di Andreas. Ho avuto lo stesso problema con il codice ES6 ma non volevo mutare le importazioni. Sembrava confuso. Quindi l'ho fatto

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

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

E aggiunto dependency.js nella cartella "__ mock __" parallelamente a dependency.js. Questo ha funzionato per me. Inoltre, questo mi ha dato la possibilità di restituire dati adeguati dall'implementazione fittizia. Assicurati di dare il percorso corretto al modulo che vuoi deridere.


Grazie per questo. Ci proverò. Piaciuto di questa soluzione troppo - stackoverflow.com/a/38414160/1882064
arcseldon

Quello che mi piace di questo approccio è che ti dà la possibilità di fornire un finto manuale per tutte le occasioni in cui vuoi deridere un modulo specifico. Ad esempio, ho un assistente di traduzione, che viene utilizzato in molti luoghi. Il __mocks__/translations.jsfile semplicemente esporta jest.fn()in modo simile a:export default jest.fn((id) => id)
Iris Schaffer,

Puoi anche usare jest.genMockFromModuleper generare mock dai moduli. facebook.github.io/jest/docs/…
Varunkumar Nagarajan,

2
Una cosa da notare è che i moduli ES6 derisi tramite export default jest.genMockFromModule('../dependency')avranno tutte le loro funzioni assegnate dependency.defaultdopo aver chiamato `jest.mock ('.. dependency'), ma altrimenti si comporteranno come previsto.
JK

7
Che aspetto ha la tua affermazione di prova? Sembra una parte importante della risposta. expect(???)
pietra

14

Inoltro rapido al 2020, ho trovato questo link come la soluzione. usando solo la sintassi del modulo 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

Anche una cosa che devi sapere (che mi ha impiegato un po 'di tempo per capire) è che non puoi chiamare jest.mock () all'interno del test; devi chiamarlo al livello superiore del modulo. Tuttavia, è possibile chiamare mockImplementation () all'interno di singoli test se si desidera impostare simulazioni diverse per test diversi.


5

La domanda ha già una risposta ma puoi risolverla in questo modo:

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

Ma "richiedere" è la sintassi di CommonJS - OP stava chiedendo dei moduli ES6
Andy il

@ Grazie mille per il tuo commento, ho aggiornato la mia risposta. BTW stessa cosa nella logica.
Slim

2

L'ho risolto in un altro modo. Supponiamo che tu abbia il tuo dependency.js

export const myFunction = () => { }

Creo un file depdency.mock.js oltre ad esso con il seguente contenuto:

export const mockFunction = jest.fn();

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

e nel test, prima di importare il file con la dipendenza che uso:

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

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

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.