Come accedere e testare una funzione interna (non di esportazione) in un modulo node.js?


181

Sto cercando di capire come testare le funzioni interne (cioè non esportate) in nodejs (preferibilmente con moka o gelsomino). E non ne ho idea!

Diciamo che ho un modulo del genere:

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

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

exports.exported = exported;

E il seguente test (moka):

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

C'è un modo per testare l'unità la notExportedfunzione senza effettivamente esportarla poiché non è destinata a essere esposta?


1
Forse esporre le funzioni per testare quando in un ambiente specifico? Non conosco la procedura standard qui.
loganfsmyth,

Risposte:


243

Il modulo rewire è sicuramente la risposta.

Ecco il mio codice per accedere a una funzione non esportata e testarla usando 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
Questa dovrebbe assolutamente essere la risposta migliore. Non richiede la riscrittura di tutti i moduli esistenti con esportazioni specifiche NODE_ENV, né implica la lettura del modulo come testo.
Adam Yost,

Bella soluzione. È possibile andare oltre e integrarlo con spie nel framework di test. Lavorando con Jasmine, ho provato questa strategia .
Franco,

2
Ottima soluzione Esiste una versione funzionante per le persone di tipo Babele?
Charles Merriam,

2
Utilizzando ReWire con battuta e TS-scherzo (dattiloscritto) ottengo il seguente errore: Cannot find module '../../package' from 'node.js'. Hai visto questo?
clu,

2
Rewire ha un problema di compatibilità con jest. Jest non prenderà in considerazione le funzioni chiamate da ricablaggio nei rapporti di copertura. Questo in qualche modo sconfigge lo scopo.
robross0606,

10

Il trucco è impostare la NODE_ENVvariabile d'ambiente su qualcosa di simile teste quindi esportarla in modo condizionale.

Supponendo di non aver installato mocha a livello globale, potresti avere un Makefile nella radice della directory dell'app che contiene quanto segue:

REPORTER = dot

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

.PHONY: test

Questo file make imposta NODE_ENV prima di eseguire mocha. È quindi possibile eseguire i test moka con make testdalla riga di comando.

Ora puoi esportare condizionalmente la tua funzione che di solito non viene esportata solo quando sono in esecuzione i test moka:

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;

L'altra risposta ha suggerito di utilizzare un modulo VM per valutare il file, ma questo non funziona e genera un errore che indica che le esportazioni non sono definite.


8
Sembra un hack, non c'è davvero modo di testare le funzioni interne (non esportate) senza farlo se il blocco NODE_ENV?
RyanHirsch,

2
È piuttosto brutto. Questo non può essere il modo migliore per risolvere questo problema.
npiv

7

MODIFICARE:

Il caricamento di un modulo mediante vmpuò causare comportamenti imprevisti (ad esempio l' instanceofoperatore non lavora più con oggetti creati in tale modulo perché i prototipi globali sono diversi da quelli utilizzati nel modulo caricato normalmente require). Non utilizzo più la tecnica seguente e utilizzo invece il modulo di ricablaggio . Funziona meravigliosamente. Ecco la mia risposta originale:

Elaborazione della risposta di srosh ...

Sembra un po 'confuso, ma ho scritto un semplice modulo "test_utils.js" che dovrebbe permetterti di fare quello che vuoi senza avere esportazioni condizionali nei tuoi moduli applicativi:

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

Ci sono alcune altre cose che sono incluse modulenell'oggetto gobal di un modulo nodo che potrebbe anche aver bisogno di andare contextnell'oggetto sopra, ma questo è il set minimo di cui ho bisogno per farlo funzionare.

Ecco un esempio usando il moka 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
puoi fare un esempio di come accedi a una funzione non esportata usando rewire?
Matthias,

1
Ehi Matthias, ti ho dato un esempio facendo esattamente questo nella mia risposta. Se ti piace, forse hai votato un paio di mie domande? :) Quasi tutte le mie domande sono rimaste a 0 e StackOverflow sta pensando di congelare le mie domande. X_X
Anthony,

2

Lavorando con Jasmine, ho cercato di approfondire la soluzione proposta da Anthony Mayfield , basata sul ricablaggio .

Ho implementato la seguente funzione ( Attenzione : non ancora accuratamente testato, appena condiviso come possibile strategia) :

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

Con una funzione come questa è possibile spiare entrambi i metodi di oggetti non esportati e le funzioni di livello superiore non esportate, come segue:

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

Quindi puoi impostare aspettative come queste:

expect(dbLoader.rewired.fs.readFileSync).toHaveBeenCalled();
expect(dbLoader.rewired.message).toHaveBeenCalledWith(POST_DESCRIPTION);

0

puoi creare un nuovo contesto usando il modulo vm e valutare il file js al suo interno, come fa sostituire sostituto. allora hai accesso a tutto ciò che dichiara.


0

Ho trovato un modo abbastanza semplice che ti consente di testare, spiare e deridere quelle funzioni interne dai test:

Diciamo che abbiamo un modulo nodo come questo:

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

function myInternalFn() {

}

function myExportableFn() {
    myInternalFn();   
}

exports.myExportableFn = myExportableFn;

Se ora vogliamo di prova e di spionaggio e finto myInternalFn pur non esportare in produzione dobbiamo migliorare il file in questo modo:

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;

Ora puoi testare, spiare e deridere myInternalFnovunque dove lo usi come testable.myInternalFne in produzione non viene esportato .


0

Questa non è una pratica consigliata, ma se non puoi usare rewirecome suggerito da @Antoine, puoi sempre semplicemente leggere il file e usarlo eval().

var fs = require('fs');
const JsFileString = fs.readFileSync(fileAbsolutePath, 'utf-8');
eval(JsFileString);

L'ho trovato utile mentre testavo i file JS sul lato client per un sistema legacy.

I file JS imposteranno molte variabili globali sotto windowsenza alcuna istruzione require(...)e module.exports(non esisteva alcun bundle di moduli come Webpack o Browserify per rimuovere comunque queste istruzioni).

Piuttosto che refactoring l'intero codebase, questo ci ha permesso di integrare unit test nel nostro JS lato client.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.