Bir node.js modülünde dahili (dışa aktarılmayan) bir işleve nasıl erişilir ve test edilir?


181

Nodejs (tercihen mocha veya jasmine ile) iç (yani ihraç değil) fonksiyonları test nasıl anlamaya çalışıyorum. Ve hiçbir fikrim yok!

Diyelim ki böyle bir modülüm var:

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

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

exports.exported = exported;

Ve aşağıdaki test (mocha):

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

notExportedFonksiyonu gerçekte dışa aktarmadan birim olarak test etmenin herhangi bir yolu var mı ?


1
Belki de sadece belirli bir ortamda ne zaman test edilebilecek işlevleri ortaya çıkarabilir? Burada standart prosedürü bilmiyorum.
loganfsmyth

Yanıtlar:


243

ReWire modülü kesinlikle cevaptır.

İşte dışarı aktarılmamış bir işleve erişmek ve Mocha kullanarak test etmek için kodum.

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
Bu kesinlikle en iyi cevap olmalı. Varolan tüm modüllerin NODE_ENV'e özel dışa aktarmalarla yeniden yazılmasını veya modülde metin olarak okumayı gerektirmez.
Adam Yost

Güzel çözüm. Daha ileri gitmek ve test çerçevenizdeki casuslarla entegre etmek mümkündür. Yasemin ile çalışarak bu stratejiyi denedim .
Franco

2
Harika bir çözüm. Babil tipi insanlar için çalışan bir versiyon var mı?
Charles Merriam

2
Rewire kullanma jest ve ts-jest (typescript) aşağıdaki hatayı alıyorum ile: Cannot find module '../../package' from 'node.js'. Bunu gördün mü?
clu

2
Rewire'ın jest ile bir uyumluluk sorunu var. Jest, kapsama raporlarında yeniden kablolama denilen işlevleri dikkate almayacaktır. Bu bir şekilde amacı yendi.
robross0606

10

İşin püf noktası, NODE_ENVortam değişkenini benzer bir şeye ayarlamak testve ardından koşullu olarak dışa aktarmaktır.

Dünya çapında mocha yüklemediğinizi varsayarsak, uygulama dizininizin kökünde aşağıdakileri içeren bir Makefile dosyası olabilir:

REPORTER = dot

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

.PHONY: test

Bu marka dosyası mocha'yı çalıştırmadan önce NODE_ENV'yi kurar. Daha sonra mocha testlerinizi make testkomut satırında çalıştırabilirsiniz.

Artık, yalnızca mocha testleriniz çalışırken dışa aktarılmayan işlevinizi koşullu olarak dışa aktarabilirsiniz:

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;

Diğer cevap, dosyayı değerlendirmek için bir vm modülünün kullanılmasını önerdi, ancak bu işe yaramıyor ve dışa aktarmanın tanımlanmadığını belirten bir hata veriyor.


8
Bu bir saldırı gibi görünüyor, eğer NODE_ENV bloğu yapmadan dahili (ihraç edilmeyen) fonksiyonları test etmenin gerçekten bir yolu yok mu?
RyanHirsch

2
Bu çok kötü. Bu sorunu çözmenin en iyi yolu olamaz.
npiv

7

DÜZENLE:

Modül kullanarak bir modül vmyüklemek beklenmedik davranışlara neden olabilir (örn. instanceofOperatör artık böyle bir modülde oluşturulan nesnelerle çalışmaz, çünkü global prototipler normal olarak yüklenen modülde kullanılanlardan farklıdır require). Artık aşağıdaki tekniği kullanmıyorum ve bunun yerine rewire modülünü kullanıyorum. Harika çalışıyor. İşte benim orijinal cevabım:

Srosh'un cevabı üzerinde duruluyor ...

Biraz kibirli hissettiriyor, ancak uygulama modüllerinizde koşullu dışa aktarmalar olmadan istediğinizi yapmanıza izin vermesi gereken basit bir "test_utils.js" modülü yazdım:

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

Bir düğüm modülünün gobal modulenesnesine dahil olan ve contextyukarıdaki nesneye de girmesi gerekebilecek daha fazla şey var , ancak çalışması için gereken minimum kümedir.

Mocha BDD kullanan bir örnek:

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
kullanarak dışa aktarılmayan bir işleve nasıl eriştiğinize bir örnek verebilir misiniz rewire?
Matthias

1
Merhaba Matthias, cevabımda tam olarak bunu yapan bir örnek verdim. Eğer hoşuna giderse, belki de birkaç sorum var mı? :) Hemen hemen tüm sorularım 0 oturdu ve StackOverflow sorularımı dondurmayı düşünüyor. X_X
Anthony

2

Jasmine ile çalışarak Anthony Mayfield'ın rewire dayalı çözümüyle daha derine inmeye çalıştım .

Aşağıdaki işlevi uyguladım ( Dikkat : henüz tam olarak test edilmedi, sadece olası bir strateji olarak paylaşıldı) :

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

Bunun gibi bir işlevle, hem dışa aktarılmayan nesnelerin hem de dışa aktarılmayan üst düzey işlevlerin her ikisinde de casusluk yapabilirsiniz:

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

Ardından şu gibi beklentiler belirleyebilirsiniz:

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

0

vm modülünü kullanarak yeni bir bağlam yapabilir ve içindeki js dosyasını değerlendirirsiniz, örneğin repl gibi. beyan ettiği her şeye erişebilirsiniz.


0

Bunları içsel olarak test etmenizi, casusluk etmenizi ve alay etmenizi sağlayan oldukça basit bir yol buldum fonksiyonları testlerin içinden :

Diyelim ki böyle bir düğüm modülümüz var:

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

function myInternalFn() {

}

function myExportableFn() {
    myInternalFn();   
}

exports.myExportableFn = myExportableFn;

Şimdi üretimde dışa aktarırken test etmek , casusluk yapmak ve alay etmek istiyorsak , dosyayı şu şekilde geliştirmemiz gerekir:myInternalFn

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;

Artık myInternalFnonu kullandığınız her yerde test edebilir, casuslayabilir ve alay edebilirsiniz testable.myInternalFnve üretimde ihraç edilmez .


0

Bu uygulama tavsiye edilmez, ancak rewire@Antoine tarafından önerilen şekilde kullanamazsanız, her zaman dosyayı okuyabilir ve kullanabilirsiniz eval().

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

Eski bir sistem için istemci tarafı JS dosyalarını birim sınarken bu yararlı buldum.

JS dosyaları küresel değişkenler altında bir sürü kuracak windowolmadan herhangi require(...)vemodule.exports deyimleri kurar (bu ifadeleri yine de kaldırmak için Webpack veya Browserify gibi bir modül paketleyicisi yoktu).

Bu, tüm kod tabanını yeniden düzenlemek yerine, müşteri tarafı JS'ye birim testlerini entegre etmemizi sağladı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.