Saya memiliki modul ES6 berikut:
network.js
export function getDataFromServer() {
return ...
}
widget.js
import { getDataFromServer } from 'network.js';
export class Widget() {
constructor() {
getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
render() {
...
}
}
Saya mencari cara untuk menguji Widget dengan contoh tiruan getDataFromServer
. Jika saya menggunakan <script>
modul terpisah s bukannya ES6, seperti di Karma, saya bisa menulis tes saya seperti:
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Namun, jika saya menguji modul ES6 secara individual di luar browser (seperti dengan Mocha + babel), saya akan menulis sesuatu seperti:
import { Widget } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(?????) // How to mock?
.andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Oke, tapi sekarang getDataFromServer
tidak tersedia di window
(well, tidak ada window
sama sekali), dan saya tidak tahu cara menyuntikkan barang langsung ke dalam widget.js
ruang lingkup sendiri.
Jadi kemana saya harus pergi dari sini?
- Apakah ada cara untuk mengakses ruang lingkup
widget.js
, atau setidaknya mengganti impornya dengan kode saya sendiri? - Jika tidak, bagaimana saya bisa membuat
Widget
diuji?
Hal-hal yang saya pertimbangkan:
Sebuah. Injeksi ketergantungan manual.
Hapus semua impor dari widget.js
dan harapkan penelepon memberikan deps.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
Saya sangat tidak nyaman dengan mengacaukan antarmuka publik Widget seperti ini dan mengekspos detail implementasi. Tidak pergi.
b. Ekspos impor untuk memungkinkan mengejek mereka.
Sesuatu seperti:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
kemudian:
import { Widget, deps } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(deps.getDataFromServer) // !
.andReturn("mockData");
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Ini kurang invasif tetapi mengharuskan saya untuk menulis banyak boilerplate untuk setiap modul, dan masih ada risiko saya menggunakan, getDataFromServer
bukan deps.getDataFromServer
sepanjang waktu. Saya gelisah tentang hal itu, tapi sejauh ini ide terbaik saya.
createSpy
( github.com/jasmine/jasmine/blob/… ) dengan referensi yang diimpor ke getDataFromServer dari modul 'network.js'. Jadi, dalam file tes widget Anda akan mengimpor getDataFromServer, dan kemudian akanlet spy = createSpy('getDataFromServer', getDataFromServer)
spyOn
pada objek itu, diimpor darinetwork.js
modul. Itu selalu referensi ke objek yang sama.Widget
antarmuka publik?Widget
kacau tanpadeps
. Mengapa tidak membuat ketergantungan secara eksplisit?Jawaban:
Saya sudah mulai menggunakan
import * as obj
gaya dalam pengujian saya, yang mengimpor semua ekspor dari modul sebagai properti dari objek yang kemudian dapat diejek. Saya menemukan ini menjadi jauh lebih bersih daripada menggunakan sesuatu seperti rewire atau proxyquire atau teknik serupa lainnya. Saya sudah melakukan ini paling sering ketika perlu mengejek tindakan Redux, misalnya. Inilah yang mungkin saya gunakan untuk contoh Anda di atas:Jika fungsi Anda adalah ekspor default, maka
import * as network from './network'
akan menghasilkan{default: getDataFromServer}
dan Anda dapat mengejek network.default.sumber
import * as obj
satu - satunya dalam tes atau juga dalam kode reguler Anda?[method_name] is not declared writable or has no setter
yang masuk akal karena impor es6 konstan. Apakah ada cara untuk mengatasinya?import
(tidak sepertirequire
, yang dapat pergi ke mana saja) diangkat, sehingga Anda tidak dapat mengimpor secara teknis beberapa kali. Kedengarannya mata-mata Anda dipanggil ke tempat lain? Untuk menjaga agar tes tidak mengacaukan negara (dikenal sebagai polusi uji), Anda dapat mengatur ulang mata-mata Anda di AfterEach (mis. Sinon.sandbox). Jasmine, saya yakin melakukan ini secara otomatis.import
dalam JS mereka tidak benar-benar menggunakan modul ES6. Sesuatu seperti webpack atau babel akan masuk saat build-time dan mengubahnya menjadi mekanisme internal mereka sendiri untuk memanggil bagian kode yang jauh (misalnya__webpack_require__
) atau menjadi salah satu standar de facto pra-ES6 , CommonJS, AMD atau UMD. Dan konversi itu seringkali tidak secara khusus mengikuti spesifikasi. Jadi bagi banyak, banyak pengembang baru saja, jawaban ini berfungsi dengan baik. Untuk sekarang.@carpeliam benar tetapi perhatikan bahwa jika Anda ingin memata-matai suatu fungsi dalam modul dan menggunakan fungsi lain dalam modul yang memanggil fungsi itu, Anda perlu memanggil fungsi itu sebagai bagian dari ekspor namespace jika mata-mata tidak akan digunakan.
Contoh yang salah:
Contoh yang tepat:
sumber
exports.myfunc2
adalah referensi langsungmyfunc2
hinggaspyOn
menggantinya dengan referensi ke fungsi mata-mata.spyOn
akan mengubah nilaiexports.myfunc2
dan menggantinya dengan objek mata-mata, sedangkanmyfunc2
tetap tidak tersentuh dalam lingkup modul (karenaspyOn
tidak memiliki akses ke sana)*
membekukan objek dan atribut objek tidak dapat diubah?export function
bersama denganexports.myfunc2
secara teknis pencampuran commonjs dan sintaks modul ES6 dan ini tidak diperbolehkan dalam versi yang lebih baru dari Webpack (2+) yang membutuhkan semua-atau-tidak penggunaan ES6 sintaks modul. Saya menambahkan jawaban di bawah ini berdasarkan yang ini akan bekerja di lingkungan ketat ES6.Saya menerapkan pustaka yang mencoba untuk memecahkan masalah ejekan run-time dari impor kelas typescript tanpa perlu kelas asli untuk mengetahui tentang injeksi ketergantungan secara eksplisit.
Perpustakaan menggunakan
import * as
sintaks dan kemudian menggantikan objek yang diekspor asli dengan kelas rintisan. Mempertahankan keamanan jenis sehingga pengujian Anda akan rusak pada waktu kompilasi jika nama metode telah diperbarui tanpa memperbarui tes yang sesuai.Perpustakaan ini dapat ditemukan di sini: ts-mock-import .
sumber
@ vdloo jawaban membuat saya menuju ke arah yang benar, tetapi menggunakan kata kunci commonjs "ekspor" dan ES6 "ekspor" bersama-sama dalam file yang sama tidak bekerja untuk saya (webpack v2 atau yang lebih baru mengeluh). Sebagai gantinya, saya menggunakan ekspor default (bernama variabel) yang membungkus semua individu bernama modul ekspor dan kemudian mengimpor ekspor default di file tes saya. Saya menggunakan pengaturan ekspor berikut dengan mocha / sinon dan stubbing berfungsi dengan baik tanpa perlu rewire, dll.:
sumber
let MyModule
tidak diperlukan untuk menggunakan ekspor default (ini bisa menjadi objek mentah). Juga, metode ini tidak perlumyfunc1()
memanggilmyfunc2()
, ia berfungsi hanya memata-matai secara langsung.Saya menemukan sintaks ini berfungsi:
Modul saya:
Kode tes modul saya:
Lihat dokumen .
sumber
jest.mock()
harus cocok dengan nama yang digunakan dalam impor / packge.json bukan nama konstanta. Dalam dokumen, keduanya sama, tetapi dengan kode sepertiimport jwt from 'jsonwebtoken'
Anda perlu mengatur tiruannyajest.mock('jsonwebtoken')
Saya belum mencobanya sendiri, tetapi saya pikir ejekan mungkin berhasil. Ini memungkinkan Anda untuk mengganti modul nyata dengan tiruan yang telah Anda berikan. Di bawah ini adalah contoh untuk memberi Anda gambaran tentang cara kerjanya:
Sepertinya
mockery
tidak terawat lagi dan saya pikir itu hanya bekerja dengan Node.js, tetapi tidak kurang, itu adalah solusi yang rapi untuk mengejek modul yang dinyatakan sulit untuk diejek.sumber