Modül kilitlenmemişken Jest'te içe aktarılan adlandırılmış işlevle nasıl taklit edilir


111

Jest'te test etmeye çalıştığım aşağıdaki modüle sahibim:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

Yukarıda gösterildiği gibi, bazı adlandırılmış işlevleri dışa aktarır ve önemli ölçüde testFnkullanır otherFn.

Jest'te birim testimi yazarken testFn, otherFnişlevle dalga geçmek istiyorum çünkü otherFnbirim testimi etkilemek için hataların olmasını istemiyorum testFn. Sorunum, bunu yapmanın en iyi yolunun ne olduğundan emin değilim:

// myModule.test.js
jest.unmock('myModule');

import { testFn, otherFn } from 'myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    // I want to mock "otherFn" here but can't reassign
    // a.k.a. can't do otherFn = jest.fn()
  });
});

Herhangi bir yardım / anlayış takdir edilmektedir.


7
Ben bunu yapmazdım. Alay etmek genellikle zaten yapmak isteyeceğiniz bir şey değildir. Ve bir şeyle dalga geçmeniz gerekiyorsa (sunucu çağrıları / vb. Nedeniyle), o zaman otherFnayrı bir modüle çıkarmanız ve bununla dalga geçmeniz gerekir .
kentcdodds

2
Ayrıca @jrubins'in kullandığı aynı yaklaşımı test ediyorum. function AKimin aradığını test et function Bama gerçek uygulamasını yürütmek function Bistemiyorum çünkü sadece içinde uygulanan mantığı test etmek istiyorumfunction A
jplaza

44
@kentcdodds, "Alay etmek genellikle yapmak istediğiniz bir şey değildir" derken neyi kastettiğinizi açıklayabilir misiniz? Alay etmek kesinlikle (en azından bazı) iyi nedenlerle sıklıkla kullanılan bir şey olduğundan, bu oldukça geniş (aşırı geniş?) Bir ifade gibi görünüyor. Öyleyse, burada alay etmenin neden iyi olmadığına mı atıfta bulunuyorsunuz yoksa genel olarak gerçekten mi demek istiyorsunuz?
Andrew Willems

2
Genellikle alay, uygulama ayrıntılarını test etmektir. Özellikle bu seviyede, testlerinizin çalıştığı gerçeğinden çok daha fazlasını doğrulamayan testlere yol açar (kodunuzun çalıştığından değil).
kentcdodds

3
Kayıt için, yıllar önce bu soruyu yazdığımdan beri, ne kadar alay etmek istediğime dair fikrimi değiştirdim (ve artık böyle alay etmeyin). Bugünlerde @kentcdodds'a ve onun test felsefesine çok katılıyorum (ve blogunu ve @testing-library/reactoradaki herhangi bir Reacters için şiddetle tavsiye ediyorum) ama bunun tartışmalı bir konu olduğunu biliyorum.
Jon Rubins

Yanıtlar:


100

jest.requireActual()İçeride kullanınjest.mock()

jest.requireActual(moduleName)

Modülün bir sahte uygulama alıp almayacağına dair tüm kontrolleri atlayarak sahte yerine asıl modülü döndürür.

Misal

İhtiyaç duyduğunuz ve döndürülen nesneye yayıldığınız bu kısa kullanımı tercih ederim:

// myModule.test.js

jest.mock('./myModule.js', () => (
  {
    ...(jest.requireActual('./myModule.js')),
    otherFn: jest.fn()
  }
))

import { otherFn } from './myModule.js'

describe('test category', () => {
  it('tests something about otherFn', () => {
    otherFn.mockReturnValue('foo')
    expect(otherFn()).toBe('foo')
  })
})

Bu yöntem aynı zamanda Jest'in El Kitabı Mocks belgelerinde de belirtilmiştir ( Örneklerin sonuna yakın ):

Manuel bir modelin ve gerçek uygulamasının senkronize kalmasını sağlamak için, gerçek modülün jest.requireActual(moduleName)manuel modelinizde kullanılmasını ve dışa aktarmadan önce onu sahte işlevlerle değiştirmesini zorunlu kılmak yararlı olabilir .


4
Fwiw, returnifadeyi kaldırarak ve ok işlev gövdesini parantez içine alarak bunu daha da kısa hale getirebilirsiniz : ör. jest.mock('./myModule', () => ({ ...jest.requireActual('./myModule'), otherFn: () => {}}))
Nick F

2
Bu harika çalıştı! Kabul edilen cevap bu olmalıdır.
JRJurman

2
...jest.requireActualBen çünkü düzgün benim için değil çalışma yaptı yol-aliasing ile ya .. İşleri Babel kullanarak ...require.requireActualveya yolundan yumuşatma çıkardıktan sonra
Tzahi Leh

1
otherFunBu durumda denen şeyi nasıl test edersiniz ? VarsayımotherFn: jest.fn()
Stevula

1
@Stevula Gerçek bir kullanım örneği göstermek için cevabımı güncelledim. mockReturnValueSahte sürümün orijinal yerine çağrıldığını daha iyi göstermek için yöntemi gösteriyorum, ancak gerçekten sadece dönüş değerine karşı iddiada bulunmadan çağrılıp çağrılmadığını görmek istiyorsanız, jest eşleştiriciyi kullanabilirsiniz .toHaveBeenCalled().
gfullam

36
import m from '../myModule';

Benim için çalışmıyor, kullandım:

import * as m from '../myModule';

m.otherFn = jest.fn();

5
Testten sonra otherFn'nin orijinal işlevselliğini diğer testlerle karışmaması için nasıl geri yüklersiniz?
Aequitas

1
Her testten sonra alayları temizlemek için jest'i yapılandırabileceğinizi düşünüyorum. Dokümanlardan: "ClearMocks yapılandırma seçeneği, testler arasında taklitleri otomatik olarak temizlemek için kullanılabilir." clearkMocks: trueJest package.json yapılandırmasını ayarlayabilirsiniz . facebook.github.io/jest/docs/en/mock-function-api.html
Cole

2
bu küresel durumunu değiştirme böyle bir sorun olması durumunda her zaman test değişkeni çeşit içindeki orijinal işlevselliğini depolamak ve test sonra geri getirebilir
BOBU

1
const orijinal; beforeAll (() => {original = m.otherFn; m.otherFn = jest.fn ();}) afterAll (() => {m.otherFn = original;}) çalışmalı, ancak ben test etmedim it
bobu

Çok teşekkür ederim! Bu benim sorunumu çözdü.
Slobodan Krasavčević

25

Görünüşe göre bu partiye geç kaldım, ama evet, bu mümkün.

testFnsadece otherFn modülü kullanarak aramanız gerekiyor .

Eğer testFnkullanımları modülü çağırmak otherFnsonra için modül ihracat otherFnalay edilebilir ve testFnmock arayacak.


İşte çalışan bir örnek:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    const mock = jest.spyOn(myModule, 'otherFn');  // spy on otherFn
    mock.mockReturnValue('mocked value');  // mock the return value

    expect(myModule.testFn()).toBe('mocked value');  // SUCCESS

    mock.mockRestore();  // restore otherFn
  });
});

2
Bu aslında bir yaklaşımla Facebook'ta kullanılan ve ortasında bir Facebook dev tarafından açıklanan bir ES6 sürümüdür bu yazı .
Brian Adams

myModule'u kendisine aktarmak yerine, sadeceexports.otherFn()
andrhamm

3
@andrhamm ES6'da exportsyok. exports.otherFn()ES6 daha önceki bir modül sözdizimine göre derlendiği için arama şu anda çalışıyor, ancak ES6 yerel olarak desteklendiğinde bozulacak.
Brian Adams

Şu anda tam olarak bu sorunu yaşıyorum ve eminim bu sorunla daha önce karşılaştım. Ağacın sallanmasına yardımcı olmak için bir sürü dışa aktarımı kaldırmak zorunda kaldım. <methodname> birçok testi geçemedi. Bunun herhangi bir etkisi olup olmadığını göreceğim, ama çok zor görünüyor. Bu soruna defalarca rastladım ve diğer yanıtların da söylediği gibi babel-plugin-rewire veya daha iyisi, npmjs.com/package/rewiremock gibi şeyler yukarıdakileri de yapabileceğinden oldukça eminim.
Astridax

Bir dönüş değeri ile dalga geçmek yerine, bir atış alay etmek mümkün mü? Düzenleme: yapabilirsiniz, işte nasıl stackoverflow.com/a/50656680/2548010
Big Money

10

Aktarılan kod babel'in otherFn()atıfta bulunan bağlamayı almasına izin vermez . Bir işlev ifadesi kullanırsanız, alay etmeyi başarabilmelisiniz otherFn().

// myModule.js
exports.otherFn = () => {
  console.log('do something');
}

exports.testFn = () => {
  exports.otherFn();

  // do other things
}

 

// myModule.test.js
import m from '../myModule';

m.otherFn = jest.fn();

Ancak @ kentcdodds önceki yorumda belirtildiği gibi, muhtemelen alay etmek istemezsiniz otherFn(). Bunun yerine, yeni bir şartname yazın otherFn()ve yaptığı tüm gerekli çağrılarla alay edin.

Örneğin, otherFn()bir http talebinde bulunuluyorsa ...

// myModule.js
exports.otherFn = () => {
  http.get('http://some-api.com', (res) => {
    // handle stuff
  });
};

Burada, http.getalay edilen uygulamalarınıza göre iddialarınızla dalga geçmek ve bunları güncellemek istersiniz .

// myModule.test.js
jest.mock('http', () => ({
  get: jest.fn(() => {
    console.log('test');
  }),
}));

1
ya otherFn ve testFn birkaç başka modül tarafından kullanılıyorsa? Bu 2 modülü kullanan (yığın ne kadar derin olursa olsun) tüm test dosyalarında http modelini kurmanız gerekir mi? Ayrıca, testFn için zaten bir testiniz varsa, neden testFn kullanan modüllerde http yerine doğrudan testFn'i saplamayasınız?
2017

1
bu nedenle otherFnbozulursa, buna bağlı tüm testlerde başarısız olur. Ayrıca otherFniçinde 5 ifs varsa , testFntüm bu alt durumlar için iyi çalışıp çalışmadığını test etmeniz gerekebilir . Şimdi test etmek için çok daha fazla kod yolunuz olacak.
Totty.js

6

Bunun uzun zaman önce sorulduğunu biliyorum ama tam da bu durumla karşılaştım ve sonunda işe yarayacak bir çözüm buldum. Bu yüzden burada paylaşacağımı düşündüm.

Modül için:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

Aşağıdakilere geçebilirsiniz:

// myModule.js

export const otherFn = () => {
  console.log('do something');
}

export const testFn = () => {
  otherFn();

  // do other things
}

bunları işlevler yerine sabitler olarak dışa aktarmak. Sorunun JavaScript'te yükseltme yapmak ve constbu davranışı kullanmakla ilgisi olduğuna inanıyorum .

Ardından testinizde aşağıdaki gibi bir şeye sahip olabilirsiniz:

import * as myModule from 'myModule';


describe('...', () => {
  jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return');

  // or

  myModule.otherFn = jest.fn(() => {
    // your mock implementation
  });
});

Taklitleriniz artık normalde beklediğiniz gibi çalışmalıdır.


2

Sorunumu burada bulduğum yanıtların bir karışımıyla çözdüm:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});

0

Buradaki ilk cevabın yanı sıra, içe aktarılan adlandırılmış işlevle alay etmek için babel-plugin-rewire'ı da kullanabilirsiniz. Adlandırılmış fonksiyon yeniden kablolaması için bölüme yüzeysel olarak bakabilirsiniz .

Buradaki durumunuz için acil faydalardan biri, işlevinizden diğer işlevi çağırma şeklinizi değiştirmenize gerek olmamasıdır.


Babel-plugin-rewire node.js ile çalışacak şekilde nasıl yapılandırılır?
Timur Gilauri
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.