ईएस 6 मॉड्यूल के आयात का मजाक कैसे उड़ाया जाए?


141

मेरे पास निम्नलिखित ईएस 6 मॉड्यूल हैं:

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() {
    ...
  }
}

मैं एक नकली उदाहरण के साथ विजेट का परीक्षण करने के लिए एक रास्ता देख रहा हूँ getDataFromServer। अगर मैं <script>कर्म में ईएस 6 मॉड्यूल के बजाय अलग-अलग एस का उपयोग करता हूं, तो मैं अपना परीक्षण लिख सकता हूं जैसे:

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

हालांकि, अगर मैं एक ब्राउज़र के बाहर व्यक्तिगत रूप से ईएस 6 मॉड्यूल का परीक्षण कर रहा हूं (जैसे कि मोचा + बाबेल के साथ), मैं कुछ इस तरह लिखूंगा:

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

ठीक है, लेकिन अब (अच्छी तरह से, वहाँ getDataFromServerबिल्कुल नहीं है) में उपलब्ध नहीं है , और मैं सीधे खुद के दायरे में सामान इंजेक्ट करने का एक तरीका नहीं जानता ।windowwindowwidget.js

अब मुझे यहां से कहां जाना है?

  1. क्या इसके दायरे को एक्सेस करने का कोई तरीका है widget.js, या कम से कम इसके आयात को मेरे स्वयं के कोड से बदल दिया जाए?
  2. यदि नहीं, तो मैं Widgetपरीक्षण योग्य कैसे बना सकता हूं ?

सामान मैंने माना:

ए। मैनुअल निर्भरता इंजेक्शन।

सभी आयातों को निकालें widget.jsऔर कॉल करने वाले से डिप्स प्रदान करने की अपेक्षा करें।

export class Widget() {
  constructor(deps) {
    deps.getDataFromServer("dataForWidget")
    .then(data => this.render(data));
  }
}

मैं इस तरह से विजेट के सार्वजनिक इंटरफ़ेस को गड़बड़ाने और कार्यान्वयन विवरण को उजागर करने के साथ बहुत असहज हूं। नही जाओ।


ख। आयात को बेनकाब करने की अनुमति दें।

कुछ इस तरह:

import { getDataFromServer } from 'network.js';

export let deps = {
  getDataFromServer
};

export class Widget() {
  constructor() {
    deps.getDataFromServer("dataForWidget")
    .then(data => this.render(data));
  }
}

फिर:

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

यह कम आक्रामक है लेकिन मुझे प्रत्येक मॉड्यूल के लिए बहुत सारे बॉयलरप्लेट लिखने की आवश्यकता है, और हर समय के getDataFromServerबजाय मेरे उपयोग का जोखिम अभी भी है deps.getDataFromServer। मैं इसे लेकर असहज हूं, लेकिन यह मेरा अब तक का सबसे अच्छा विचार है।


अगर इस तरह के आयात के लिए कोई देशी नकली समर्थन नहीं है, तो मैं शायद अपने ईएस 6 शैली के आयात को कस्टम मॉकएबल आयात प्रणाली में परिवर्तित करने के लिए खुद के ट्रांसफार्मर को लिखने पर विचार करूंगा। यह निश्चित रूप से संभव विफलता की एक और परत जोड़ देगा और उस कोड को बदल देगा जिसे आप परीक्षण करना चाहते हैं, ...।
t.niese

मैं अभी एक परीक्षण सूट सेट नहीं कर सकता, लेकिन मैं 'network.jpg' मॉड्यूल से getDataFromServer के लिए एक आयातित संदर्भ के साथ jasmin createSpy( github.com/jasmine/jasmine/blob/… ) फ़ंक्शन का उपयोग करने का प्रयास करूँगा । ताकि, विजेट की परीक्षण फ़ाइल में आप getDataFromServer आयात करेंगे, और फिरlet spy = createSpy('getDataFromServer', getDataFromServer)
माइक्रोफाइड किया

दूसरा अनुमान 'नेटवर्क.जेएस' मॉड्यूल से एक वस्तु को वापस करना है, एक फ़ंक्शन नहीं। इस तरह, आप spyOnउस वस्तु पर, network.jsमॉड्यूल से आयात कर सकते हैं । यह हमेशा उसी वस्तु का संदर्भ होता है।
माइक्रोफाइड

वास्तव में, यह पहले से ही एक वस्तु है, जो मैं देख सकता हूं: babeljs.io/repl/…
माइक्रोफाइड

2
मुझे वास्तव में समझ नहीं आ रहा है कि निर्भरता इंजेक्शन Widgetसार्वजनिक इंटरफ़ेस को कैसे गड़बड़ कर देता है? बिनाWidget गड़बड़ है । निर्भरता को स्पष्ट क्यों नहीं किया जाता है? deps
Thebearingedge

जवाबों:


129

मैंने import * as objअपने परीक्षणों के भीतर शैली को नियोजित करना शुरू कर दिया है , जो एक मॉड्यूल से सभी निर्यातों को एक वस्तु के गुणों के रूप में आयात करता है जिसे तब मॉक किया जा सकता है। मुझे लगता है कि यह rewire या छद्म या इसी तरह की किसी तकनीक का उपयोग करने की तुलना में बहुत अधिक क्लीनर है। मैंने यह सबसे अधिक बार किया है जब उदाहरण के लिए, Redux कार्यों का मजाक उड़ाने की आवश्यकता होती है। यहां मैं आपके उदाहरण के लिए उपयोग कर सकता हूं:

import * as network from 'network.js';

describe("widget", function() {
  it("should do stuff", function() {
    let getDataFromServer = spyOn(network, "getDataFromServer").andReturn("mockData")
    let widget = new Widget();
    expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
    expect(otherStuff).toHaveHappened();
  });
});

यदि आपका कार्य डिफ़ॉल्ट निर्यात होता है, तो import * as network from './network'उत्पादन होगा {default: getDataFromServer}और आप network.default को मॉक कर सकते हैं।


3
क्या आप import * as objकेवल परीक्षण में या अपने नियमित कोड में भी उपयोग करते हैं ?
चाऊ थाई

36
@carpeliam यह अभ्यस्त ES6 मॉड्यूल युक्ति के साथ काम करता है जहां आयात आसानी से होते हैं।
आशीष

7
जैस्मीन शिकायत कर रही है [method_name] is not declared writable or has no setterजो समझ में आता है क्योंकि es6 आयात निरंतर हैं। क्या वर्कअराउंड का एक तरीका है?
शाम

2
@Francisc import(विपरीत require, जो कहीं भी जा सकता है) फहराया जाता है, इसलिए आप तकनीकी रूप से कई बार आयात नहीं कर सकते। अपने जासूस की तरह लगता है कहीं और कहा जा रहा है? परीक्षण को गड़बड़ी से बचाने के लिए (परीक्षण प्रदूषण के रूप में जाना जाता है) रखने के लिए, आप अपने जासूसों को एक afterEach (जैसे sinon.sandbox) में रीसेट कर सकते हैं। जैस्मिन मेरा मानना ​​है कि यह स्वचालित रूप से करता है।
कार्पेलियम

10
@ Agent47 समस्या यह है कि जबकि ES6 कल्पना विशेष रूप से इस जवाब को काम करने से रोकती है, ठीक उसी तरह जिस तरह से आपने उल्लेख किया है, ज्यादातर लोग जो importअपने JS में लिखते हैं , वे वास्तव में ES6 मॉड्यूल का उपयोग नहीं कर रहे हैं। वेबपैक या बैबेल जैसा कुछ बिल्ड-टाइम में कदम होगा और इसे या तो कोड के सुदूर हिस्सों (जैसे __webpack_require__) या पूर्व-ईएस 6 डी फैक्टो मानकों में से एक, कॉमनजस, एएमडी या यूएमडी में बदलने के लिए अपने स्वयं के आंतरिक तंत्र में बदल देगा । और वह रूपांतरण अक्सर कल्पना का सख्ती से पालन नहीं करता है। इतने के लिए, कई देवता अभी, यह जवाब ठीक काम करता है। अभी के लिए।
डेमोनेक्समाचीना

31

@carpeliam सही है लेकिन ध्यान दें कि यदि आप किसी मॉड्यूल में किसी फ़ंक्शन पर जासूसी करना चाहते हैं और उस फ़ंक्शन को कॉल करने वाले मॉड्यूल में किसी अन्य फ़ंक्शन का उपयोग करते हैं, तो आपको उस फ़ंक्शन को निर्यात नाम के भाग के रूप में कॉल करने की आवश्यकता है अन्यथा जासूस का उपयोग नहीं किया जाएगा।

गलत उदाहरण:

// mymodule.js

export function myfunc2() {return 2;}
export function myfunc1() {return myfunc2();}

// tests.js
import * as mymodule

describe('tests', () => {
    beforeEach(() => {
        spyOn(mymodule, 'myfunc2').and.returnValue = 3;
    });

    it('calls myfunc2', () => {
        let out = mymodule.myfunc1();
        // out will still be 2
    });
});

सही उदाहरण:

export function myfunc2() {return 2;}
export function myfunc1() {return exports.myfunc2();}

// tests.js
import * as mymodule

describe('tests', () => {
    beforeEach(() => {
        spyOn(mymodule, 'myfunc2').and.returnValue = 3;
    });

    it('calls myfunc2', () => {
        let out = mymodule.myfunc1();
        // out will be 3 which is what you expect
    });
});

4
काश मैं 20 से अधिक बार इस जवाब को वोट कर सकता! धन्यवाद!
sfletche

क्या कोई समझा सकता है कि ऐसा क्यों है? Export.myfunc2 () myfunc2 () की एक प्रति प्रत्यक्ष संदर्भ के बिना है?
कॉलिन व्हिटमर्ष

2
@ColinWhitmarsh exports.myfunc2एक प्रत्यक्ष संदर्भ है myfunc2जब तक कि spyOnइसे जासूसी फ़ंक्शन के संदर्भ में प्रतिस्थापित नहीं किया जाता है। spyOnके मूल्य को बदल देगा exports.myfunc2और इसे जासूसी वस्तु से बदल देगा, जबकि myfunc2मॉड्यूल के दायरे में अछूता रहता है (क्योंकि spyOnइसकी कोई पहुंच नहीं है)
madprog

*वस्तु को फ्रीज के साथ आयात नहीं करना चाहिए और वस्तु विशेषताओं को बदला नहीं जा सकता है?
एजेंट

1
बस ध्यान दें कि तकनीकी रूप से आम और ES6 मॉड्यूल सिंटैक्स export functionके साथ उपयोग करने की यह सिफारिश की exports.myfunc2जाती है और इसे Webpack (2+) के नए संस्करणों में अनुमति नहीं दी जाती है जिनके लिए सभी-या-कुछ नहीं ES6 मॉड्यूल सिंटैक्स उपयोग की आवश्यकता होती है। मैंने इस एक के आधार पर नीचे एक उत्तर जोड़ा है जो ईएस 6 सख्त वातावरण में काम करेगा।
QuarkleMotion

6

मैंने एक पुस्तकालय लागू किया है जो किसी भी स्पष्ट निर्भरता इंजेक्शन के बारे में जानने के लिए मूल वर्ग की आवश्यकता के बिना टाइपस्क्रिप्ट श्रेणी के आयात के रन-टाइम मॉकिंग के मुद्दे को हल करने का प्रयास करता है।

लाइब्रेरी import * asसिंटैक्स का उपयोग करता है और फिर मूल निर्यात की गई वस्तु को स्टब क्लास से बदल देता है। यह प्रकार की सुरक्षा को बनाए रखता है इसलिए आपके परीक्षण संकलन समय पर टूट जाएंगे यदि विधि नाम को संबंधित परीक्षण को अपडेट किए बिना अपडेट किया गया है।

यह पुस्तकालय यहां पाया जा सकता है: ts-mock-import


1
इस मॉड्यूल को और अधिक गितुब सितारों की आवश्यकता है
एसडी

6

@ vdloo का उत्तर मुझे सही दिशा में ले गया, लेकिन दोनों समान "निर्यात" और ES6 मॉड्यूल "निर्यात" कीवर्ड का एक ही फ़ाइल में एक साथ उपयोग करने से मेरे लिए काम नहीं किया (वेबपैक v2 या बाद में शिकायत करता है)। इसके बजाय, मैं एक डिफ़ॉल्ट (नामित वैरिएबल) निर्यात का उपयोग कर रहा हूं, जो मॉड्यूल नामित निर्यात के सभी व्यक्तिगत रैपिंग को लपेटता है और फिर मेरी परीक्षण फ़ाइल में डिफ़ॉल्ट निर्यात का आयात करता है। मैं मोचा / सिनॉन और स्टबिंग के साथ निम्नलिखित निर्यात सेटअप का उपयोग कर रहा हूं, बिना आवश्यकता के ठीक से काम करना, आदि:

// MyModule.js
let MyModule;

export function myfunc2() { return 2; }
export function myfunc1() { return MyModule.myfunc2(); }

export default MyModule = {
  myfunc1,
  myfunc2
}

// tests.js
import MyModule from './MyModule'

describe('MyModule', () => {
  const sandbox = sinon.sandbox.create();
  beforeEach(() => {
    sandbox.stub(MyModule, 'myfunc2').returns(4);
  });
  afterEach(() => {
    sandbox.restore();
  });
  it('myfunc1 is a proxy for myfunc2', () => {
    expect(MyModule.myfunc1()).to.eql(4);
  });
});

मददगार जवाब, धन्यवाद। बस यह उल्लेख करना चाहता था कि let MyModuleडिफ़ॉल्ट निर्यात का उपयोग करने की आवश्यकता नहीं है (यह एक कच्ची वस्तु हो सकती है)। इसके अलावा, इस पद्धति myfunc1()को कॉल करने की आवश्यकता नहीं है myfunc2(), यह सीधे उस पर जासूसी करने के लिए काम करता है।
मार्क एडिंगटन

@QuarkleMotion: ऐसा प्रतीत होता है कि आपने इसे दुर्घटना के कारण अपने मुख्य खाते से अलग खाते के साथ संपादित किया है। इसीलिए आपके संपादन को एक मैनुअल अनुमोदन से गुजरना पड़ा - ऐसा नहीं लग रहा था कि यह आप से था मुझे लगता है कि यह सिर्फ एक दुर्घटना थी, लेकिन, अगर यह जानबूझकर था, तो आपको जुर्राब की कठपुतली खातों पर आधिकारिक नीति पढ़नी चाहिए ताकि आप गलती से नियमों का उल्लंघन न करें
संकल्पी कंपाइलर

1
@ConspicuousCompiler सिर के लिए धन्यवाद - यह एक गलती थी, मैंने अपने काम के ईमेल-एसओ खाते के साथ इस उत्तर को संशोधित करने का इरादा नहीं किया।
क्वार्कलेमोशन

यह एक अलग प्रश्न का उत्तर प्रतीत होता है! कहाँ है widget.js और network.js? इस उत्तर में कोई सकरात्मक निर्भरता नहीं है, जिसने मूल प्रश्न को कठिन बना दिया है।
बेनेट मैकलेवे

3

मैंने इस वाक्य रचना को काम करते पाया है:

मेरा मॉड्यूल:

// mymod.js
import shortid from 'shortid';

const myfunc = () => shortid();
export default myfunc;

मेरे मॉड्यूल का परीक्षण कोड:

// mymod.test.js
import myfunc from './mymod';
import shortid from 'shortid';

jest.mock('shortid');

describe('mocks shortid', () => {
  it('works', () => {
    shortid.mockImplementation(() => 1);
    expect(myfunc()).toEqual(1);
  });
});

डॉक्टर को देखें ।


+1 और कुछ अतिरिक्त निर्देशों के साथ: केवल नोड मॉड्यूल के साथ काम करने लगता है, जो आपके पास पैकेज पर है। और अधिक महत्वपूर्ण बात यह है कि जेस्ट डॉक्स में उल्लेखित कुछ भी नहीं है, स्ट्रिंग jest.mock()को निरंतर के नाम के बजाय आयात / packge.json में उपयोग किए गए नाम से मेल खाना है। डॉक्स में वे दोनों समान हैं, लेकिन कोड की तरह import jwt from 'jsonwebtoken'आपको सेटअप की आवश्यकता होती है जैसे किjest.mock('jsonwebtoken')
कास्केलोटी

0

मैंने खुद इसकी कोशिश नहीं की है, लेकिन मुझे लगता है कि नकली काम हो सकता है। यह आपको वास्तविक मॉड्यूल को एक मॉक के साथ स्थानापन्न करने की अनुमति देता है जो आपने प्रदान किया है। नीचे एक उदाहरण दिया गया है जिससे आपको पता चलता है कि यह कैसे काम करता है:

mockery.enable();
var networkMock = {
    getDataFromServer: function () { /* your mock code */ }
};
mockery.registerMock('network.js', networkMock);

import { Widget } from 'widget.js';
// This widget will have imported the `networkMock` instead of the real 'network.js'

mockery.deregisterMock('network.js');
mockery.disable();

ऐसा लगता है कि mockeryअब इसका रखरखाव नहीं किया गया है और मुझे लगता है कि यह केवल Node.js के साथ काम करता है, लेकिन कोई भी कम नहीं है, यह मॉकिंग मॉड्यूल के लिए एक साफ समाधान है जो अन्यथा नकली करने के लिए कठिन है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.