2012-07-11 19 views
126

मेरे पास एक एएमडी मॉड्यूल है जिसे मैं परीक्षण करना चाहता हूं, लेकिन मैं वास्तविक निर्भरताओं को लोड करने की बजाय इसकी निर्भरताओं का नकल करना चाहता हूं। मैं requirejs उपयोग कर रहा हूँ, और मेरे मॉड्यूल के लिए कोड इस तरह दिखता है:मैं RequJS में यूनिट परीक्षण के लिए निर्भरता का नकल कैसे कर सकता हूं?

define(['hurp', 'durp'], function(Hurp, Durp) { 
    return { 
    foo: function() { 
     console.log(Hurp.beans) 
    }, 
    bar: function() { 
     console.log(Durp.beans) 
    } 
    } 
} 

मैं कैसे पता hurp नकली कर सकते हैं और durp तो मैं प्रभावी रूप से इकाई परीक्षण कर सकते हैं?

+0

मैं सिर्फ 'define' समारोह बाहर उपहास करने के लिए Node.js में कुछ पागल eval सामान कर रहा हूं। यद्यपि कुछ अलग विकल्प हैं। मैं आशा करता हूं कि यह सहायक होगा। – jergason

+1

जैस्मीन के साथ यूनिट परीक्षण के लिए आप [जसक] (https://github.com/biril/jasq) पर एक त्वरित नज़र डालना भी चाह सकते हैं। [अस्वीकरण: मैं lib को बनाए रख रहा हूं] – biril

+1

यदि आप नोड एनवी में परीक्षण कर रहे हैं तो आप [requ-mock] (https://github.com/ValeriiVasin/requirejs-mock) पैकेज का उपयोग कर सकते हैं। यह आपको आसानी से अपनी निर्भरताओं का नकल करने की अनुमति देता है, मॉड्यूल इत्यादि को प्रतिस्थापित करता है। यदि आपको एसिंक मॉड्यूल लोड के साथ ब्राउज़र एनवी की आवश्यकता है - तो आप [Squire.js] (https://github.com/iammerrick/Squire.js/) – ValeriiVasin

उत्तर

16

मुझे इस समस्या के तीन अलग-अलग समाधान मिल गए हैं, उनमें से कोई भी सुखद नहीं है।

डिफाइनिंग निर्भरता इनलाइन

define('hurp', [], function() { 
    return { 
    beans: 'Beans' 
    }; 
}); 

define('durp', [], function() { 
    return { 
    beans: 'durp beans' 
    }; 
}); 

require('hurpdhurp', function() { 
    // test hurpdurp in here 
}); 

फगली। आपको अपने एएमडी बॉयलरप्लेट के साथ अपने परीक्षणों को अव्यवस्थित करना होगा।

अलग अलग रास्तों

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

नोड

यह मेरे वर्तमान समाधान है, लेकिन अभी भी एक भयानक एक है में नकली यह।

आप अपना खुद का define फ़ंक्शन मॉड्यूल में अपने स्वयं के मैक्स प्रदान करने और कॉलबैक में अपने परीक्षण डालने के लिए बनाते हैं। तो फिर तुम इतने तरह अपने परीक्षण चलाने के लिए, मॉड्यूल eval:

var fs = require('fs') 
    , hurp = { 
     beans: 'BEANS' 
    } 
    , durp = { 
     beans: 'durp beans' 
    } 
    , hurpDurp = fs.readFileSync('path/to/hurpDurp', 'utf8'); 
    ; 



function define(deps, cb) { 
    var TestableHurpDurp = cb(hurp, durp); 
    // now run tests below on TestableHurpDurp, which is using your 
    // passed-in mocks as dependencies. 
} 

// evaluate the AMD module, running your mocked define function and your tests. 
eval(hurpDurp); 

यह मेरा पसंदीदा समाधान है। यह थोड़ा जादू दिखता है, लेकिन इसमें कुछ फायदे हैं।

  1. नोड में अपने परीक्षण चलाएं, इसलिए ब्राउज़र स्वचालन के साथ कोई गड़बड़ नहीं है।
  2. आपके परीक्षणों में गन्दा एएमडी बॉयलरप्लेट की कम आवश्यकता।
  3. आप क्रोध में eval का उपयोग करते हैं, और क्रैकफोर्ड क्रोध के साथ विस्फोट की कल्पना करते हैं।

यह अभी भी कुछ कमियां हैं, जाहिर है।

  1. चूंकि आप नोड में परीक्षण कर रहे हैं, आप ब्राउज़र ईवेंट या डोम मैनिपुलेशन के साथ कुछ भी नहीं कर सकते हैं। तर्क परीक्षण के लिए केवल अच्छा है।
  2. अभी भी स्थापित करने के लिए थोड़ा सा गुंजाइश है। आपको प्रत्येक परीक्षण में define का नकल करने की आवश्यकता है, क्योंकि वह वही है जहां आपके परीक्षण वास्तव में चलते हैं।

मैं एक परीक्षण धावक पर काम कर रहा हूँ सामान के इस प्रकार के लिए एक अच्छे वाक्य रचना देने के लिए, लेकिन मैं अभी भी समस्या के लिए कोई अच्छा समाधान है 1.

निष्कर्ष

requirejs में deps मजाक कठिन बेकार । मुझे एक तरीका मिला जो काम करता है, लेकिन अभी भी इससे बहुत खुश नहीं हूं। यदि आपके पास कोई बेहतर विचार है तो कृपया मुझे बताएं।

64

तो मैं एक समाधान requirejs config फ़ंक्शन का उपयोग करें कि अपने परीक्षण के लिए एक नया संदर्भ बनाने के लिए के साथ आया था this post पढ़ने के बाद जहां आप बस अपने निर्भरता नकली कर सकते हैं:

var cnt = 0; 
function createContext(stubs) { 
    cnt++; 
    var map = {}; 

    var i18n = stubs.i18n; 
    stubs.i18n = { 
    load: sinon.spy(function(name, req, onLoad) { 
     onLoad(i18n); 
    }) 
    }; 

    _.each(stubs, function(value, key) { 
    var stubName = 'stub' + key + cnt; 

    map[key] = stubName; 

    define(stubName, function() { 
     return value; 
    }); 
    }); 

    return require.config({ 
    context: "context_" + cnt, 
    map: { 
     "*": map 
    }, 
    baseUrl: 'js/cfe/app/' 
    }); 
} 

तो यह एक नया संदर्भ में, जहां बनाता है Hurp और Durp की परिभाषा आपके द्वारा फ़ंक्शन में पारित वस्तुओं द्वारा निर्धारित की जाएगी। नाम के लिए Math.random शायद थोड़ा गंदा है लेकिन यह काम करता है। क्योंकि यदि आपके पास परीक्षण का एक समूह होगा तो आपको अपने मोजे का पुन: उपयोग रोकने के लिए प्रत्येक सूट के लिए नया संदर्भ बनाना होगा, या जब आप वास्तविक आवश्यकता मॉड्यूल चाहते हैं तो मैक्स लोड करना होगा।

आपके मामले में यह इस तरह दिखेगा:

(function() { 

    var stubs = { 
    hurp: 'hurp', 
    durp: 'durp' 
    }; 
    var context = createContext(stubs); 

    context(['yourModuleName'], function (yourModule) { 

    //your normal jasmine test starts here 

    describe("yourModuleName", function() { 
     it('should log', function(){ 
     spyOn(console, 'log'); 
     yourModule.foo(); 

     expect(console.log).toHasBeenCalledWith('hurp'); 
     }) 
    }); 
    }); 
})(); 

तो मैं थोड़ी देर और उसके वास्तव में मजबूत के लिए उत्पादन में इस दृष्टिकोण का उपयोग कर रहा हूँ।

+1

को आजमा सकते हैं आप यहां क्या कर रहे हैं ... खासकर जब से आप प्रत्येक परीक्षण के लिए एक अलग संदर्भ लोड कर सकते हैं। एकमात्र चीज जो मैं चाहता हूं मैं बदल सकता हूं कि ऐसा लगता है कि यह केवल तभी काम कर रहा है जब मैं सभी निर्भरताओं का मज़ाक उड़ाता हूं। क्या आप नकली वस्तुओं को वापस करने के तरीके के बारे में जानते हैं यदि वे वहां हैं, लेकिन यदि नकली प्रदान नहीं किया जाता है तो वास्तविक .js फ़ाइल से पुनर्प्राप्त करने के लिए फ़ॉलबैक? मैं इसे समझने के लिए आवश्यक कोड के माध्यम से खोदने की कोशिश कर रहा हूं, लेकिन मुझे थोड़ा खो गया है। –

+5

यह केवल 'createContext' फ़ंक्शन पर निर्भर निर्भरता को झुकाता है। तो आपके मामले में यदि आप केवल कार्य करने के लिए '{hurp: 'hurp'} पास करते हैं, तो 'durp' फ़ाइल सामान्य निर्भरता के रूप में लोड की जाएगी। –

+0

आप सही हैं ... सुनिश्चित नहीं है कि मैं पहली बार गलत क्या कर रहा था। महान काम, यह काम में आने वाला है ... बहुत बहुत धन्यवाद !!! –

15

config.map विकल्प http://requirejs.org/docs/api.html#config-map है।

पर यह कैसे उपयोग करें:

  1. सामान्य मॉड्यूल को परिभाषित करें;
  2. स्टब मॉड्यूल को परिभाषित करें;
  3. आवश्यकता जेएस को निश्चित रूप से कॉन्फ़िगर करें;

    requirejs.config({ 
        map: { 
        'source/js': { 
         'foo': 'normalModule' 
        }, 
        'source/test': { 
         'foo': 'stubModule' 
        } 
        } 
    }); 
    

आप foo मॉड्यूल जो वास्तविक मॉड्यूल संदर्भ और उसके अनुसार ठूंठ हो जाएगा इस्तेमाल कर सकते हैं सामान्य और परीक्षण कोड के लिए इस मामले में।

+0

यह दृष्टिकोण मेरे लिए वास्तव में अच्छा काम करता है। मेरे मामले में, मैंने इसे परीक्षण धावक पृष्ठ के HTML में जोड़ा -> मानचित्र: {\t \t \t '*': {'सामान्य/मॉड्यूल/उपयोगी मॉड्यूल': '/Tests/Specs/Common/usefulModuleMock.js'}} – Aligned

9

आप निर्भरता के लिए testr.js का उपयोग कर सकते हैं। आप मूल के बजाय नकली निर्भरताओं को लोड करने के लिए testr सेट कर सकते हैं।

var fakeDep = function(){ 
    this.getText = function(){ 
     return 'Fake Dependancy'; 
    }; 
}; 

var Module1 = testr('module1', { 
    'dependancies/dependancy1':fakeDep 
}); 

इस बाहर की जाँच के रूप में अच्छी तरह से: यहाँ एक उदाहरण उपयोग है http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/

+2

मैं वास्तव में testr.js काम करने के लिए चाहता था, लेकिन यह अभी तक कार्य तक काफी महसूस नहीं करता है। अंत में मैं @ एंड्रियास कोबरले के समाधान के साथ जा रहा हूं, जो मेरे परीक्षणों (सुंदर नहीं) में नेस्टेड संदर्भ जोड़ देगा लेकिन जो लगातार काम करता है। मेरी इच्छा है कि कोई इस समाधान को और अधिक सुरुचिपूर्ण तरीके से हल करने पर ध्यान केंद्रित कर सके। मैं testr.js देख रहा हूं और यदि यह काम करता है, तो स्विच करेगा। –

+0

@shioyama हाय, प्रतिक्रिया के लिए धन्यवाद! मुझे आपके टेस्ट स्टैक के भीतर testr.js को कॉन्फ़िगर करने के तरीके को देखना अच्छा लगेगा। आपके पास होने वाले किसी भी मुद्दे को ठीक करने में आपकी सहायता करने के लिए खुश! यदि आप वहां कुछ लॉग करना चाहते हैं तो जिथब मुद्दे पृष्ठ भी है। धन्यवाद, –

+1

@MattyF खेद मैं भी अब सही याद नहीं है क्या सही कारण था कि testr.js मेरे लिए काम नहीं था, लेकिन मैं निष्कर्ष है कि अतिरिक्त संदर्भों के उपयोग वास्तव में काफी ठीक है और में है पर आए हैं तथ्य के साथ लाइन में कैसे आवश्यक है। जेएस का इस्तेमाल मॉकिंग/स्टबिंग के लिए किया जाना था। –

44

आप डॉक्स से नए Squire.js lib

की जाँच करने के लिए चाहते हो सकता है:

Squire.js है मॉकिंग निर्भरताओं को आसान बनाने के लिए Requ.js उपयोगकर्ताओं के लिए एक निर्भरता इंजेक्टर!

+5

स्क्वायर मेरे लिए बहुत अच्छी तरह से काम किया। –

+2

मजबूत अनुशंसित! मैं squire.js का उपयोग करने के लिए अपना कोड अपडेट कर रहा हूं और अब तक मुझे यह बहुत पसंद है। बहुत ही सरल कोड, हुड के नीचे कोई महान जादू नहीं है, लेकिन इस तरह से किया जाता है (अपेक्षाकृत) समझने में आसान है। –

+0

मुझे स्क्वायर पक्ष के साथ अन्य परीक्षणों को प्रभावित करने में बहुत सारी समस्याएं आई हैं और इसकी अनुशंसा नहीं कर सकती है। मैं सिफारिश करेंगे https://www.npmjs.com/package/requirejs-mock –

1

आप कुछ सादे js परीक्षण जो एक इकाई को अलग करना चाहते हैं, तो आप बस इस स्निपेट का उपयोग कर सकते हैं:

function define(args, func){ 
    if(!args.length){ 
     throw new Error("please stick to the require.js api which wants a: define(['mydependency'], function(){})"); 
    } 

    var fileName = document.scripts[document.scripts.length-1].src; 

    // get rid of the url and path elements 
    fileName = fileName.split("/"); 
    fileName = fileName[fileName.length-1]; 

    // get rid of the file ending 
    fileName = fileName.split("."); 
    fileName = fileName[0]; 

    window[fileName] = func; 
    return func; 
} 
window.define = define; 
2

इस उत्तर Andreas Köberle's answer पर आधारित है।
मेरे समाधान को कार्यान्वित करने और समझने के लिए यह इतना आसान नहीं था, इसलिए मैं इसे और अधिक विस्तार से समझाऊंगा कि यह कैसे काम करता है, और कुछ नुकसान से बचने के लिए, उम्मीद है कि इससे भविष्य के आगंतुकों की मदद मिलेगी।

तो, सभी सेटअप के पहले:
मैं कर्मा परीक्षण ढांचे के रूप में परीक्षण धावक के रूप में और MochaJs उपयोग कर रहा हूँ।

TypeError: Cannot read property 'call' of undefined

RequireJs अन्य मॉड्यूल के लिए map मॉड्यूल आईडी की संभावना है:

स्क्वायर की तरह कुछ का उपयोग करना, मेरे लिए काम नहीं किया किसी कारण से, जब मैं इसका इस्तेमाल किया, परीक्षण ढांचे त्रुटियों फेंक दिया आईडी। यह require function बनाने की भी अनुमति देता है जो वैश्विक require की तुलना में different config का उपयोग करता है।
इस समाधान के लिए काम करने के लिए ये सुविधाएं महत्वपूर्ण हैं।

यहां नकली कोड का मेरा संस्करण है, जिसमें (बहुत) टिप्पणियां हैं (मुझे उम्मीद है कि यह समझ में आता है)। मैंने इसे मॉड्यूल के अंदर लपेट लिया, ताकि परीक्षणों को आसानी से इसकी आवश्यकता हो।

define([], function() { 
    var count = 0; 
    var requireJsMock= Object.create(null); 
    requireJsMock.createMockRequire = function (mocks) { 
     //mocks is an object with the module ids/paths as keys, and the module as value 
     count++; 
     var map = {}; 

     //register the mocks with unique names, and create a mapping from the mocked module id to the mock module id 
     //this will cause RequireJs to load the mock module instead of the real one 
     for (property in mocks) { 
      if (mocks.hasOwnProperty(property)) { 
       var moduleId = property; //the object property is the module id 
       var module = mocks[property]; //the value is the mock 
       var stubId = 'stub' + moduleId + count; //create a unique name to register the module 

       map[moduleId] = stubId; //add to the mapping 

       //register the mock with the unique id, so that RequireJs can actually call it 
       define(stubId, function() { 
        return module; 
       }); 
      } 
     } 

     var defaultContext = requirejs.s.contexts._.config; 
     var requireMockContext = { baseUrl: defaultContext.baseUrl }; //use the baseUrl of the global RequireJs config, so that it doesn't have to be repeated here 
     requireMockContext.context = "context_" + count; //use a unique context name, so that the configs dont overlap 
     //use the mapping for all modules 
     requireMockContext.map = { 
      "*": map 
     }; 
     return require.config(requireMockContext); //create a require function that uses the new config 
    }; 

    return requireJsMock; 
}); 

सबसे बड़ी ख़तरा मैं का सामना करना पड़ा है, जिसका शाब्दिक मुझे घंटे खर्च, RequireJs config बनाने गया था। मैंने इसे कॉपी करने की कोशिश की (गहरी), और केवल आवश्यक गुणों (जैसे संदर्भ या मानचित्र) को ओवरराइड करें। यह काम नहीं करता! केवल baseUrl कॉपी करें, यह ठीक काम करता है।

प्रयोग

इसके इस्तेमाल के लिये अपने परीक्षण में इसकी आवश्यकता है तो mocks बनाने के लिए, और फिर इसे createMockRequire लिए गुजरती हैं।

var ModuleMock = function() { 
    this.method = function() { 
     methodCalled += 1; 
    }; 
}; 
var mocks = { 
    "ModuleIdOrPath": ModuleMock 
} 
var requireMocks = mocker.createMockRequire(mocks); 

और यहाँ एक पूर्ण परीक्षण फ़ाइल के उदाहरण: उदाहरण के लिए:

define(["chai", "requireJsMock"], function (chai, requireJsMock) { 
    var expect = chai.expect; 

    describe("Module", function() { 
     describe("Method", function() { 
      it("should work", function() { 
       return new Promise(function (resolve, reject) { 
        var handler = { handle: function() { } }; 

        var called = 0; 
        var moduleBMock = function() { 
         this.method = function() { 
          methodCalled += 1; 
         }; 
        }; 
        var mocks = { 
         "ModuleBIdOrPath": moduleBMock 
        } 
        var requireMocks = requireJsMock.createMockRequire(mocks); 

        requireMocks(["js/ModuleA"], function (moduleA) { 
         try { 
          moduleA.method(); //moduleA should call method of moduleBMock 
          expect(called).to.equal(1); 
          resolve(); 
         } catch (e) { 
          reject(e); 
         } 
        }); 
       }); 
      }); 
     }); 
    }); 
}); 
संबंधित मुद्दे