यूनिट को Node.js मॉड्यूल का परीक्षण करने के लिए कैसे अन्य मॉड्यूल की आवश्यकता होती है और वैश्विक फ़ंक्शन की आवश्यकता के लिए कैसे मॉक करें?


156

यह एक तुच्छ उदाहरण है जो मेरी समस्या की जड़ को दिखाता है:

var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.doComplexStuff();
}

module.exports = underTest;

मैं इस कोड के लिए एक इकाई परीक्षण लिखने की कोशिश कर रहा हूं। मैं फंक्शन को पूरी तरह से मॉक innerLibकिए बिना आवश्यकता का मजाक कैसे उड़ा सकता हूं require?

तो यह मैं वैश्विक का मजाक उड़ाने की कोशिश कर रहा हूं requireऔर यह पता लगा रहा हूं कि ऐसा करने से भी काम नहीं चलेगा:

var path = require('path'),
    vm = require('vm'),
    fs = require('fs'),
    indexPath = path.join(__dirname, './underTest');

var globalRequire = require;

require = function(name) {
    console.log('require: ' + name);
    switch(name) {
        case 'connect':
        case indexPath:
            return globalRequire(name);
            break;
    }
};

समस्या यह है कि फ़ाइल के requireअंदर फ़ंक्शन underTest.jsवास्तव में मॉक आउट नहीं किया गया है। यह अभी भी वैश्विक requireकार्य की ओर इशारा करता है। तो ऐसा लगता है कि मैं केवल requireउसी फ़ाइल के भीतर फ़ंक्शन को मॉक कर सकता हूं जो मैं मॉकिंग कर रहा हूं। यदि मैं requireकिसी भी चीज़ को शामिल करने के लिए वैश्विक का उपयोग करता हूं , भले ही मैंने स्थानीय प्रतिलिपि को ओवरराइड कर दिया हो, तो आवश्यक फाइलें अभी भी होंगी। वैश्विक requireसंदर्भ।


आपको ओवरराइट करना होगा global.require। चर moduleडिफ़ॉल्ट रूप से लिखते हैं क्योंकि मॉड्यूल मॉड्यूल स्कॉप होते हैं।
रेयोनोस

@ रेयानोस मैं ऐसा कैसे करूंगा? Global.require अपरिभाषित है? यहां तक ​​कि अगर मैं इसे अपने स्वयं के फ़ंक्शन के साथ बदल देता हूं, तो अन्य फ़ंक्शन कभी भी उपयोग नहीं करेंगे?
HMR

जवाबों:


175

अब तुम यह कर सकते हो!

मैंने छद्म प्रकाशन प्रकाशित किया, जो आपके परीक्षण के दौरान आपके मॉड्यूल के अंदर वैश्विक आवश्यकता को ओवरराइड करने का ध्यान रखेगा

इसका मतलब है कि आवश्यक मॉड्यूल के लिए नकली इंजेक्शन लगाने के लिए आपको अपने कोड में कोई बदलाव करने की आवश्यकता नहीं है

Proxyquire में एक बहुत ही सरल एपीआई है जो उस मॉड्यूल को हल करने की अनुमति देता है जिसे आप एक सरल चरण में अपने आवश्यक मॉड्यूल के लिए मोक्स / स्टब्स के साथ परीक्षण और पास करने की कोशिश कर रहे हैं।

@ रयोनोस सही है कि परंपरागत रूप से आपको इसके लिए बहुत आदर्श समाधानों का सहारा नहीं लेना था ताकि इसके बदले विकास हो या नीचे का विकास हो

जो मुख्य कारण है कि मैंने प्रॉक्सीविर बनाया - बिना किसी परेशानी के टॉप-डाउन टेस्ट संचालित विकास की अनुमति देने के लिए।

दस्तावेज़ीकरण पर नज़र डालें और उदाहरणों को गेज करने के लिए यदि यह आपकी आवश्यकताओं के अनुरूप होगा।


5
मैं छद्म का उपयोग करता हूं और मैं पर्याप्त अच्छी बातें नहीं कह सकता। इसने मुझे बचा लिया! मुझे ऐप्लिसिटर टाइटेनियम में विकसित एक ऐप के लिए चमेली-नोड परीक्षण लिखने का काम सौंपा गया था जो कुछ मॉड्यूल को पूर्ण पथ और कई परिपत्र निर्भरता के लिए मजबूर करता है। प्रॉक्सीव्यू ने मुझे उन अंतरालों को रोकना और क्रॉफ़्ट को बाहर निकालने की अनुमति दी, जिनकी मुझे प्रत्येक परीक्षण की आवश्यकता नहीं थी। ( यहाँ समझाया गया है )। बहुत बहुत धन्यवाद!
सुकीमा

यह सुनकर प्रसन्नता हुई कि प्रॉक्सीक्वे ने आपको अपने कोड को ठीक से जांचने में मदद की :)
थोरस्टन लॉरेंज

1
बहुत अच्छा @ThorstenLorenz, मैं हरा दूँगा। का उपयोग करें proxyquire!
२०:१३ को २०:१३ पर बेवाक्वा

बहुत खुबस! जब मैंने स्वीकार किए गए उत्तर को देखा कि "आप नहीं कर सकते" मैंने सोचा "हे भगवान, गंभीरता से ?!" लेकिन यह वास्तव में इसे बचा लिया।
चाडविक

3
आप वेबपैक का उपयोग करने वालों के लिए, प्रॉक्सीवेर पर शोध करने में समय व्यतीत न करें। यह वेबपैक का समर्थन नहीं करता है। मैं इसके बजाय इंजेक्शन-लोडर में देख रहा हूँ ( github.com/plasticine/inject-loader )।
आर्टिफ़

116

इस मामले में एक बेहतर विकल्प मॉड्यूल की नकल करना है जो वापस आ जाता है।

बेहतर या बदतर के लिए, अधिकांश नोड.जेएस मॉड्यूल सिंगलटन हैं; कोड के दो टुकड़ों की आवश्यकता होती है () एक ही मॉड्यूल को उस मॉड्यूल के समान संदर्भ मिलता है।

आप इसका लाभ उठा सकते हैं और आवश्यक वस्तुओं को मॉक करने के लिए सिनोन की तरह कुछ का उपयोग कर सकते हैं। मोचा परीक्षण निम्नानुसार है:

// in your testfile
var innerLib  = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon     = require('sinon');

describe("underTest", function() {
  it("does something", function() {
    sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
      // whatever you would like innerLib.toCrazyCrap to do under test
    });

    underTest();

    sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion

    innerLib.toCrazyCrap.restore(); // restore original functionality
  });
});

सिओन का दावा करने के लिए ची के साथ अच्छा एकीकरण है , और मैंने आसान जासूस / ठूंठ की सफाई (परीक्षण प्रदूषण से बचने के लिए) के लिए मोचा के साथ साइनॉन को एकीकृत करने के लिए एक मॉड्यूल लिखा ।

ध्यान दें कि अंडरटेस्ट का उसी तरह से मजाक नहीं किया जा सकता है, जैसा कि अंडरटेस्ट केवल एक फ़ंक्शन देता है।

एक अन्य विकल्प जेस्ट मोक्स का उपयोग करना है। उनके पेज पर फॉलो करें


1
दुर्भाग्य से, नोड.जेएस मॉड्यूल को एकल होने की गारंटी नहीं है, जैसा कि यहां बताया गया है: justjs.com/posts/…
फ्रंटियरप्सीको

4
@FrontierPsycho कुछ चीजें: सबसे पहले, जहां तक ​​परीक्षण का संबंध है, लेख अप्रासंगिक है। जब तक आप अपनी निर्भरता (और निर्भरता की निर्भरता नहीं) का परीक्षण कर रहे हैं, तब तक आपके सभी कोड आपके पास एक ही ऑब्जेक्ट प्राप्त करने जा रहे हैं require('some_module'), क्योंकि आपके सभी कोड समान नोड_मॉड्यूल्स को साझा करते हैं। दूसरा, लेख सिंगलनेट्स के साथ नामस्थान को भ्रमित कर रहा है, जो ऑर्थोगोनल की तरह है। तीसरा, वह लेख बहुत पुराना है (जहाँ तक नोड.जेएस का संबंध है) पुराना है, इसलिए जो दिन में वापस मान्य हो सकता था वह अब मान्य नहीं है।
इलियट फोस्टर

2
हम्म। जब तक कि हम में से कोई एक वास्तव में कोड को खोदता है जो एक बिंदु या दूसरे को साबित करता है, मैं निर्भरता इंजेक्शन के आपके समाधान के साथ जाऊंगा, या बस आसपास की वस्तुओं को पास करूंगा, यह अधिक सुरक्षित और भविष्य का प्रमाण है।
फ्रंटियरस्पीको

1
मुझे यकीन नहीं है कि आप क्या साबित करने के लिए कह रहे हैं। नोड मॉड्यूल के सिंगलटन (कैश्ड) प्रकृति को आमतौर पर समझा जाता है। निर्भरता इंजेक्शन, जबकि एक अच्छा मार्ग, उचित मात्रा में अधिक बॉयलर प्लेट और अधिक कोड हो सकता है। DI सांख्यिकीय रूप से टाइप की जाने वाली भाषाओं में अधिक आम है, जहाँ आपके कोड में गतिशील रूप से बतख-पंच जासूस / स्टब / मॉक करना कठिन है। पिछले तीन वर्षों में मैंने जो कई परियोजनाएं की हैं, वे मेरे उत्तर में वर्णित विधि का उपयोग करती हैं। यह सभी विधियों में सबसे आसान है, हालांकि मैं इसे संयम से उपयोग करता हूं।
इलियट फोस्टर

1
मेरा सुझाव है कि आप sinon.js पर पढ़ें। यदि आप साइनॉन का उपयोग कर रहे हैं (जैसा कि ऊपर दिए गए उदाहरण में है) तो आप या तो आराम करेंगे या innerLib.toCrazyCrap.restore()साइनॉन को कॉल करेंगे, sinon.stub(innerLib, 'toCrazyCrap')जिसके माध्यम से आपको यह बदलने की अनुमति मिलती है कि स्टब कैसे व्यवहार करता है innerLib.toCrazyCrap.returns(false):। इसके अलावा, rewire proxyquireऊपर के विस्तार के समान ही बहुत अधिक लगता है ।
इलियट फोस्टर

11

मैं नकली-आवश्यकता का उपयोग करता हूं । सुनिश्चित करें कि आप requireपरीक्षण किए जाने वाले मॉड्यूल से पहले अपने मोक्स को परिभाषित करते हैं ।


स्टॉप (<file>) या stopAll () को तुरंत करने के लिए अच्छा है ताकि आपको एक परीक्षण में कैश्ड फ़ाइल न मिले जहां आपको मॉक नहीं चाहिए।
जस्टिन क्रूस

1
इससे एक टन की मदद मिली।
दिवार

2

मॉकिंग requireमुझे एक बुरा हैक की तरह लगता है। मैं व्यक्तिगत रूप से इसे टालने की कोशिश करूंगा और इसे और अधिक परीक्षण योग्य बनाने के लिए कोड को फिर से रिफ्लेक्टर करूंगा। निर्भरता को संभालने के लिए विभिन्न दृष्टिकोण हैं।

1) तर्कों के रूप में निर्भरताएं पास करें

function underTest(innerLib) {
    return innerLib.doComplexStuff();
}

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

2) मॉड्यूल को एक वर्ग के रूप में लागू करें, फिर निर्भरता प्राप्त करने के लिए वर्ग विधियों / गुणों का उपयोग करें

(यह एक विरोधाभासी उदाहरण है, जहाँ कक्षा का उपयोग उचित नहीं है, लेकिन यह विचार को व्यक्त करता है) (ES6 उदाहरण)

const innerLib = require('./path/to/innerLib')

class underTestClass {
    getInnerLib () {
        return innerLib
    }

    underTestMethod () {
        return this.getInnerLib().doComplexStuff()
    }
}

अब आप आसानी से getInnerLibअपने कोड का परीक्षण करने के लिए विधि को स्टब कर सकते हैं । कोड अधिक क्रियात्मक हो जाता है, लेकिन परीक्षण करना भी आसान होता है।


1
मुझे नहीं लगता कि यह आप के रूप में हैसी है ... यह मजाक का बहुत सार है। आवश्यक निर्भरता का मजाक उड़ाना चीजों को इतना सरल बना देता है कि यह कोड संरचना को बदले बिना डेवलपर को नियंत्रण प्रदान करता है। आपके तरीके बहुत ही वाचाल हैं और इसलिए इनके बारे में तर्क करना कठिन है। मैं इस पर छद्म या नकली की आवश्यकता का चयन करता हूं; मुझे यहाँ कोई समस्या नहीं दिख रही है। कोड साफ और आसानी से कारण और याद रखने वाले अधिकांश लोग हैं जो इसे पहले से पढ़े हुए कोड को पढ़ चुके हैं जो आप उन्हें जटिल बनाना चाहते हैं। यदि ये लिबास हैक किए गए हैं, तो आपकी परिभाषा से मॉकिंग और स्टबिंग भी हैक है और इसे रोका जाना चाहिए।
इमैनुएल महुनी

1
दृष्टिकोण नंबर 1 के साथ समस्या यह है कि आप स्टैक तक आंतरिक कार्यान्वयन विवरण पास कर रहे हैं। कई परतों के साथ यह तब आपके मॉड्यूल का उपभोक्ता बनने के लिए और अधिक जटिल हो जाता है। यह एक आईओसी कंटेनर जैसे दृष्टिकोण के साथ काम कर सकता है ताकि निर्भरताएं आपके लिए स्वचालित रूप से इंजेक्ट हो जाएं, हालांकि ऐसा महसूस होता है कि हमारे पास पहले से ही आयात विवरण के माध्यम से नोड मॉड्यूल में इंजेक्शन निर्भरताएं हैं, फिर यह समझ में आता है कि उन्हें उस स्तर पर मॉक करने में सक्षम होना चाहिए ।
Magritte

1) यह समस्या को दूसरी फ़ाइल में ले जाता है 2) अभी भी अन्य मॉड्यूल को लोड करता है और इस प्रकार ओवरहेड प्रदर्शन करता है, और संभवतः साइड इफेक्ट्स का कारण बनता है (जैसे लोकप्रिय colorsमॉड्यूल जो गड़बड़ करता है String.prototype)
थॉमस 22

2

यदि आपने कभी jest का उपयोग किया है, तो आप शायद jest के मॉक फ़ीचर से परिचित हैं।

"Jest.mock (...)" का उपयोग करके आप बस उस स्ट्रिंग को निर्दिष्ट कर सकते हैं जो आपके कोड में कहीं भी आवश्यकता-कथन में होगी और जब भी उस स्ट्रिंग का उपयोग करके मॉड्यूल की आवश्यकता होगी, तो इसके बजाय एक मॉक-ऑब्जेक्ट लौटाया जाएगा।

उदाहरण के लिए

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})

पूरी तरह से "फ़ायरबेज़-एडमिन" के सभी आयात / आवश्यकता को उस वस्तु से बदल देगा, जो आप उस "फ़ैक्टरी" -फंक्शन से लौटे थे।

ठीक है, आप ऐसा कर सकते हैं कि जब जेस्ट का उपयोग किया जाता है क्योंकि जेस्ट हर मॉड्यूल के चारों ओर रनटाइम बनाता है और मॉड्यूल में आवश्यकता के "हुक" संस्करण को इंजेक्ट करता है, लेकिन आप बिना जेस्ट के ऐसा करने में सक्षम नहीं होंगे।

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

इसे संबोधित करने के लिए मैंने दो एनपीएम-मॉड्यूल बनाए हैं जिनका उपयोग आप जो चाहें प्राप्त कर सकते हैं।

आपको एक बेबल-प्लगइन और एक मॉड्यूल मॉकर की आवश्यकता है।

निम्नलिखित विकल्पों के साथ अपने -babelrc में babel-plugin-mock-requirement प्लगइन का उपयोग करें:

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...

और अपनी परीक्षण फ़ाइल में जेस्ट जैसे-मॉक मॉड्यूल का उपयोग करें:

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...

jestlike-mockमॉड्यूल अभी भी बहुत rudimental है और प्रलेखन का एक बहुत नहीं है लेकिन वहाँ बहुत कोड या तो नहीं है। मैं और अधिक पूर्ण सुविधा सेट के लिए किसी भी पीआर की सराहना करता हूं। लक्ष्य पूरे "jest.mock" सुविधा को फिर से बनाना होगा।

यह देखने के लिए कि "jest-runtime" पैकेज में कोड को कैसे देख सकते हैं, यह कैसे लागू होता है। उदाहरण के लिए, https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 देखें , यहां वे एक मॉड्यूल का "ऑटोमॉक" उत्पन्न करते हैं।

उम्मीद है की वो मदद करदे ;)


1

आप नहीं कर सकते। आपको अपने यूनिट टेस्ट सूट का निर्माण करना होगा ताकि सबसे कम मॉड्यूल का पहले परीक्षण किया जाए और उच्च स्तर के मॉड्यूल की आवश्यकता होती है जो बाद में परीक्षण किए जाते हैं।

आपको यह भी मानना ​​होगा कि कोई भी 3 पार्टी कोड और नोड.जेएस ही अच्छी तरह से परीक्षण किया गया है।

मुझे लगता है कि आप निकट भविष्य में आने वाले चौखटे को देख सकते हैं global.require

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

// underTest.js
var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.toCrazyCrap();
}

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}

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


2
यह मानते हुए कि तीसरे पक्ष के कोड का अच्छी तरह से परीक्षण किया गया है, आईएमओ के काम करने का एक शानदार तरीका नहीं है।
henry.oswald

5
@ यह काम करने का एक शानदार तरीका है। यह आपको केवल उच्च गुणवत्ता वाले तीसरे पक्ष के कोड के साथ काम करने या आपके कोड के सभी टुकड़ों को लिखने के लिए
मजबूर

ठीक है, मुझे लगा कि आप अपने कोड और तीसरे पक्ष के कोड के बीच एकीकरण परीक्षण नहीं करने की बात कर रहे हैं। माना।
henry.oswald

1
एक "यूनिट टेस्ट सूट" सिर्फ यूनिट टेस्ट का एक संग्रह है, लेकिन यूनिट टेस्ट एक दूसरे से स्वतंत्र होना चाहिए, इसलिए यूनिट इन यूनिट टेस्ट। प्रयोग करने योग्य होने के लिए, यूनिट परीक्षण तेज और स्वतंत्र होंगे, ताकि आप स्पष्ट रूप से देख सकें कि यूनिट परीक्षण विफल होने पर कोड कहां टूट गया है।
एंड्रियास बेरहेम ब्रूडिन

यह मेरे लिए काम नहीं किया। मॉड्यूल ऑब्जेक्ट "var innerLib ..." आदि को उजागर नहीं करता है
AnitKryst

1

आप नकली पुस्तकालय का उपयोग कर सकते हैं :

describe 'UnderTest', ->
  before ->
    mockery.enable( warnOnUnregistered: false )
    mockery.registerMock('./path/to/innerLib', { doComplexStuff: -> 'Complex result' })
    @underTest = require('./path/to/underTest')

  it 'should compute complex value', ->
    expect(@underTest()).to.eq 'Complex result'

1

जिज्ञासु के लिए मॉक मॉड्यूल का सरल कोड

उन हिस्सों पर require.cacheध्यान दें जहाँ आप और नोट require.resolveविधि में हेरफेर करते हैं क्योंकि यह गुप्त चटनी है।

class MockModules {  
  constructor() {
    this._resolvedPaths = {} 
  }
  add({ path, mock }) {
    const resolvedPath = require.resolve(path)
    this._resolvedPaths[resolvedPath] = true
    require.cache[resolvedPath] = {
      id: resolvedPath,
      file: resolvedPath,
      loaded: true,
      exports: mock
    }
  }
  clear(path) {
    const resolvedPath = require.resolve(path)
    delete this._resolvedPaths[resolvedPath]
    delete require.cache[resolvedPath]
  }
  clearAll() {
    Object.keys(this._resolvedPaths).forEach(resolvedPath =>
      delete require.cache[resolvedPath]
    )
    this._resolvedPaths = {}
  }
}

उपयोग की तरह :

describe('#someModuleUsingTheThing', () => {
  const mockModules = new MockModules()
  beforeAll(() => {
    mockModules.add({
      // use the same require path as you normally would
      path: '../theThing',
      // mock return an object with "theThingMethod"
      mock: {
        theThingMethod: () => true
      }
    })
  })
  afterAll(() => {
    mockModules.clearAll()
  })
  it('should do the thing', async () => {
    const someModuleUsingTheThing = require('./someModuleUsingTheThing')
    expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
  })
})

लेकिन ... प्रॉक्सी बहुत बढ़िया है और आपको इसका उपयोग करना चाहिए। यह आपकी आवश्यकता को केवल परीक्षणों के लिए स्थानीयकृत ओवरराइड करता है और मैं इसकी अत्यधिक अनुशंसा करता हूं।

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