2013-02-14 7 views
80

Próbuję dowiedzieć się, jak przetestować funkcje wewnętrzne (tj. Nie eksportowane) w nodejs (najlepiej z mocha lub jaśminu). I nie mam pojęcia!Jak uzyskać dostęp i przetestować funkcję wewnętrzną (nie eksportową) w module node.js?

powiedzmy Mam moduł tak:

function exported(i) { 
    return notExported(i) + 1; 
} 

function notExported(i) { 
    return i*2; 
} 

exports.exported = exported; 

i następujący test (mocca):

var assert = require('assert'), 
    test = require('../modules/core/test'); 

describe('test', function(){ 

    describe('#exported(i)', function(){ 
    it('should return (i*2)+1 for any given i', function(){ 
     assert.equal(3, test.exported(1)); 
     assert.equal(5, test.exported(2)); 
    }); 
    }); 
}); 

Czy istnieje jakiś sposób na badanej jednostki funkcja notExported bez faktycznie eksportując go od to nie ma być odsłonięte?

+1

Może wystawiać funkcje do testowania w określonym środowisku? Nie znam tutaj standardowej procedury. – loganfsmyth

Odpowiedz

121

Moduł rewire to zdecydowanie odpowiedź.

Oto mój kod dostępu do niezbadanej funkcji i testowania jej za pomocą Mocha.

application.js:

function logMongoError(){ 
    console.error('MongoDB Connection Error. Please make sure that MongoDB is running.'); 
} 

test.js:

var rewire = require('rewire'); 
var chai = require('chai'); 
var should = chai.should(); 


var app = rewire('../application/application.js'); 


logError = app.__get__('logMongoError'); 

describe('Application module', function() { 

    it('should output the correct error', function(done) { 
     logError().should.equal('MongoDB Connection Error. Please make sure that MongoDB is running.'); 
     done(); 
    }); 
}); 
+2

To absolutnie najlepsza odpowiedź. Nie wymaga przepisywania wszystkich istniejących modułów z eksportem specyficznym dla NODE_ENV, ani nie wymaga odczytu w module jako tekstu. –

+0

Piękne rozwiązanie. Możliwe jest pójście dalej i zintegrowanie go ze szpiegami w twojej strukturze testowej.Pracując z Jasmine, próbowałem [tej strategii] (http://stackoverflow.com/a/39818726/659788). – Franco

+0

Dzięki za niesamowite rozwiązanie ... zrobiłeś mój dzień :) – Sohail

0

możesz utworzyć nowy kontekst przy użyciu modułu vm i sprawdzić w nim plik js, przypominający rep. wtedy masz dostęp do wszystkiego, co deklaruje.

11

Sztuką jest ustawienie zmiennej środowiskowej NODE_ENV na coś podobnego do test, a następnie warunkowo wyeksportować ją.

Zakładając, że nie zostały zainstalowane globalnie mokka, można mieć Makefile w katalogu głównym katalogu aplikacji, która zawiera następujące elementy:

REPORTER = dot 

test: 
    @NODE_ENV=test ./node_modules/.bin/mocha \ 
     --recursive --reporter $(REPORTER) --ui bbd 

.PHONY: test 

Ten plik make konfiguruje NODE_ENV przed uruchomieniem mokka. Następnie można uruchomić testy mokki za pomocą linii poleceń w wierszu poleceń.

Teraz można warunkowo wyeksportować funkcję, która zazwyczaj nie jest eksportowany tylko, gdy są uruchomione testy mokka:

function exported(i) { 
    return notExported(i) + 1; 
} 

function notExported(i) { 
    return i*2; 
} 

if (process.env.NODE_ENV === "test") { 
    exports.notExported = notExported; 
} 
exports.exported = exported; 

Druga odpowiedź zasugerował użycie modułu VM ocenić plik, ale to nie robi” t działa i zgłasza błąd stwierdzający, że eksport nie jest zdefiniowany.

+5

To wydaje się być hackerem, czy naprawdę nie ma możliwości przetestowania wewnętrznych (nie eksportowanych) funkcji bez robienia tego, jeśli blok NODE_ENV? – RyanHirsch

+2

To dość nieprzyjemne. To nie może być najlepszy sposób na rozwiązanie tego problemu. – npiv

5

Edycja:

Włożenie modułu przy użyciu vm mogą powodować nieoczekiwane zachowanie (na przykład operator instanceof nie pracuje już z przedmiotami, które są tworzone w taki moduł z powodu globalne prototypy są różne od tych, które stosuje się w module ładowane normalnie require). Nie używam już poniższej techniki i zamiast tego używam modułu rewire. Działa wspaniale. Oto mój oryginalny odpowiedź:

Opracowanie na odpowiedź srosh za ...

Czuje się nieco hacky, ale napisałem proste „test_utils.js” moduł, który powinien pozwalają robić to, co chcesz, bez konieczności eksportu warunkowe w modułach aplikacji:

var Script = require('vm').Script, 
    fs  = require('fs'), 
    path = require('path'), 
    mod = require('module'); 

exports.expose = function(filePath) { 
    filePath = path.resolve(__dirname, filePath); 
    var src = fs.readFileSync(filePath, 'utf8'); 
    var context = { 
    parent: module.parent, paths: module.paths, 
    console: console, exports: {}}; 
    context.module = context; 
    context.require = function (file){ 
    return mod.prototype.require.call(context, file);}; 
    (new Script(src)).runInNewContext(context); 
    return context;}; 

jest kilka rzeczy, które są zawarte w Gobal module obiektu modułu węzła, który może też trzeba iść do obiektu context powyżej, ale jest to minimalny zestaw, że muszę do niego pracować.

Oto przykład przy użyciu mocha BDD:

var util = require('./test_utils.js'), 
    assert = require('assert'); 

var appModule = util.expose('/path/to/module/modName.js'); 

describe('appModule', function(){ 
    it('should test notExposed', function(){ 
    assert.equal(6, appModule.notExported(3)); 
    }); 
}); 
+2

możesz podać przykład jak uzyskać dostęp do funkcji nie wyeksportowanych za pomocą 'rewire'? – Matthias

+1

Hej Matthias, dałem wam przykład robiący dokładnie to w mojej odpowiedzi. Jeśli ci się podoba, może załapałeś kilka moich pytań? :) Prawie wszystkie moje pytania były na 0, a StackOverflow zastanawia się nad zamrożeniem moich pytań. X_X – Antoine

1

Znalazłem dość prosty sposób, który pozwala sprawdzić, szpieg i wyśmiewać tych wewnętrznych funkcji z poziomu testów:

Powiedzmy mamy moduł węzła takiego:

mymodule.js: 
------------ 
"use strict"; 

function myInternalFn() { 

} 

function myExportableFn() { 
    myInternalFn(); 
} 

exports.myExportableFn = myExportableFn; 

Jeśli teraz chcemy przetestować i szpieg i mock myInternalFnnatomiast nie eksportując go w produkcji musimy poprawić plik tak:

my_modified_module.js: 
---------------------- 
"use strict"; 

var testable;       // <-- this is new 

function myInternalFn() { 

} 

function myExportableFn() { 
    testable.myInternalFn();   // <-- this has changed 
} 

exports.myExportableFn = myExportableFn; 

             // the following part is new 
if(typeof jasmine !== "undefined") { 
    testable = exports; 
} else { 
    testable = {}; 
} 

testable.myInternalFn = myInternalFn; 

Teraz można przetestować, szpieg i makiety myInternalFn wszędzie, gdzie go używać jako testable.myInternalFn i produkcji to jest nie wyeksportowane.

1

Praca z Jasmine, starałem się głębiej z solution proposed by Anthony Mayfield, w oparciu o rewire.

I wdrożone następujące funkcje (Uwaga: jeszcze nie testowane, tak jak wspólna strategia possibile):

function spyOnRewired() { 
    const SPY_OBJECT = "rewired"; // choose preferred name for holder object 
    var wiredModule = arguments[0]; 
    var mockField = arguments[1]; 

    wiredModule[SPY_OBJECT] = wiredModule[SPY_OBJECT] || {}; 
    if (wiredModule[SPY_OBJECT][mockField]) // if it was already spied on... 
     // ...reset to the value reverted by jasmine 
     wiredModule.__set__(mockField, wiredModule[SPY_OBJECT][mockField]); 
    else 
     wiredModule[SPY_OBJECT][mockField] = wiredModule.__get__(mockField); 

    if (arguments.length == 2) { // top level function 
     var returnedSpy = spyOn(wiredModule[SPY_OBJECT], mockField); 
     wiredModule.__set__(mockField, wiredModule[SPY_OBJECT][mockField]); 
     return returnedSpy; 
    } else if (arguments.length == 3) { // method 
     var wiredMethod = arguments[2]; 

     return spyOn(wiredModule[SPY_OBJECT][mockField], wiredMethod); 
    } 
} 

Z funkcji takich jak to można szpiegować na obu metod nie- wywożone przedmioty i non-eksportowane funkcje najwyższym poziomie, w sposób następujący:

var dbLoader = require("rewire")("../lib/db-loader"); 
// Example: rewired module dbLoader 
// It has non-exported, top level object 'fs' and function 'message' 

spyOnRewired(dbLoader, "fs", "readFileSync").and.returnValue(FULL_POST_TEXT); // method 
spyOnRewired(dbLoader, "message"); // top level function 

Następnie można ustawić oczekiwania, takie jak:

expect(dbLoader.rewired.fs.readFileSync).toHaveBeenCalled(); 
expect(dbLoader.rewired.message).toHaveBeenCalledWith(POST_DESCRIPTION); 
Powiązane problemy