Làm cách nào để truy cập và kiểm tra hàm nội bộ (không xuất) trong mô-đun node.js?


181

Tôi đang cố gắng tìm ra cách kiểm tra các hàm nội bộ (tức là không xuất) trong nodejs (tốt nhất là với mocha hoặc hoa nhài). Và tôi không có ý tưởng!

Hãy nói rằng tôi có một mô-đun như thế:

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

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

exports.exported = exported;

Và bài kiểm tra sau (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));
    });
  });
});

Có cách nào để đơn vị kiểm tra notExportedchức năng mà không thực sự xuất nó vì nó không có nghĩa là bị lộ?


1
Có lẽ chỉ cần phơi bày các chức năng để kiểm tra khi trong một môi trường cụ thể? Tôi không biết thủ tục tiêu chuẩn ở đây.
loganfsmyth

Câu trả lời:


243

Các mô-đun tua lại chắc chắn là câu trả lời.

Đây là mã của tôi để truy cập một chức năng không được báo cáo và kiểm tra nó bằng 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
Điều này hoàn toàn nên là câu trả lời hàng đầu. Nó không yêu cầu viết lại tất cả các mô-đun hiện có với xuất khẩu cụ thể NODE_ENV, cũng không liên quan đến việc đọc trong mô-đun dưới dạng văn bản.
Adam Yost

Giải pháp an toàn. Có khả năng đi xa hơn và tích hợp nó với các gián điệp trong khuôn khổ thử nghiệm của bạn. Làm việc với Jasmine, tôi đã thử chiến lược này .
Franco

2
Giải pháp tuyệt vời. Có một phiên bản làm việc cho loại người Babel?
Charles Merriam

2
Sử dụng tua lại với jest và ts-jest (bản đánh máy) tôi gặp lỗi sau : Cannot find module '../../package' from 'node.js'. Bạn đã thấy cái này chưa?
clu

2
Phần thưởng có vấn đề tương thích với jest. Jest sẽ không xem xét các chức năng được gọi từ tua lại trong các báo cáo bảo hiểm. Điều đó phần nào đánh bại mục đích.
robross0606

10

Bí quyết là đặt NODE_ENVbiến môi trường thành một cái gì đó giống như testvà sau đó xuất điều kiện.

Giả sử bạn chưa cài đặt mocha trên toàn cầu, bạn có thể có Makefile trong thư mục gốc của thư mục chứa các mục sau:

REPORTER = dot

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

.PHONY: test

Điều này tạo tập tin thiết lập NODE_ENV trước khi chạy mocha. Sau đó, bạn có thể chạy thử nghiệm mocha của mình với make testdòng lệnh.

Bây giờ, bạn có thể xuất khẩu một cách có điều kiện chức năng thường không được xuất chỉ khi các bài kiểm tra mocha của bạn đang chạy:

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;

Câu trả lời khác được đề xuất sử dụng mô-đun vm để đánh giá tệp, nhưng điều này không hoạt động và đưa ra lỗi cho biết xuất khẩu không được xác định.


8
Điều này có vẻ như là một hack, có thực sự không có cách nào để kiểm tra các chức năng nội bộ (không xuất khẩu) mà không thực hiện điều đó nếu khối NODE_ENV không?
RyanHirsch

2
Điều đó thật khó chịu. Đây không thể là cách tốt nhất để giải quyết vấn đề này.
npiv

7

BIÊN TẬP:

Tải mô-đun bằng cách sử dụng vmcó thể gây ra hành vi không mong muốn (ví dụ: instanceoftoán tử không còn hoạt động với các đối tượng được tạo trong mô-đun đó vì các nguyên mẫu toàn cầu khác với các mô-đun được sử dụng trong mô-đun được tải bình thường require). Tôi không còn sử dụng các kỹ thuật dưới đây và thay vào đó sử dụng tua lại mô-đun . Nó hoạt động tuyệt vời. Đây là câu trả lời ban đầu của tôi:

Xây dựng câu trả lời của srosh ...

Tôi cảm thấy hơi khó chịu, nhưng tôi đã viết một mô-đun "test_utils.js" đơn giản cho phép bạn làm những gì bạn muốn mà không cần xuất điều kiện trong các mô-đun ứng dụng của bạn:

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

Có một số điều nữa được bao gồm trong gobal của mô-đun nút module đối tượng yêu tinh cũng có thể cần phải đi vào contextđối tượng ở trên, nhưng đây là tập hợp tối thiểu mà tôi cần để nó hoạt động.

Đây là một ví dụ sử dụng 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
bạn có thể đưa ra một ví dụ về cách bạn truy cập một chức năng không xuất khẩu bằng cách sử dụng rewire?
Matthias

1
Này Matthias, tôi đã cho bạn một ví dụ làm chính xác điều đó trong câu trả lời của tôi. Nếu bạn thích nó, có thể nêu lên một vài câu hỏi của tôi? :) Hầu như tất cả các câu hỏi của tôi đã ở mức 0 và StackOverflow đang suy nghĩ về việc đóng băng các câu hỏi của tôi. X_X
Anthony

2

Làm việc với Jasmine, tôi đã cố gắng đi sâu hơn với giải pháp do Anthony Mayfield đề xuất , dựa trên tua lại .

Tôi đã thực hiện chức năng sau ( Chú ý : chưa được kiểm tra kỹ lưỡng, chỉ được chia sẻ dưới dạng chiến lược sở hữu) :

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

Với một chức năng như thế này, bạn có thể theo dõi cả hai phương thức của các đối tượng không xuất và các hàm cấp cao nhất không xuất, như sau:

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

Sau đó, bạn có thể đặt kỳ vọng như sau:

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

0

bạn có thể tạo một bối cảnh mới bằng cách sử dụng mô-đun vm và eval tệp js trong đó, giống như thay thế. sau đó bạn có quyền truy cập vào tất cả mọi thứ nó tuyên bố.


0

Tôi đã tìm thấy một cách khá đơn giản cho phép bạn kiểm tra, theo dõi và chế nhạo các chức năng nội bộ đó từ bên trong các bài kiểm tra:

Giả sử chúng ta có một mô-đun nút như thế này:

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

function myInternalFn() {

}

function myExportableFn() {
    myInternalFn();   
}

exports.myExportableFn = myExportableFn;

Nếu bây giờ chúng tôi muốn kiểm tra gián điệp giả định myInternalFn trong khi không xuất nó trong sản xuất, chúng tôi phải cải thiện tệp như thế này:

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;

Bây giờ bạn có thể kiểm tra, gián điệp và giả myInternalFnở mọi nơi bạn sử dụng nó testable.myInternalFnvà trong sản xuất, nó không được xuất khẩu .


0

Đây không phải là thực hành được khuyến nghị, nhưng nếu bạn không thể sử dụng rewiretheo đề xuất của @Antoine, bạn luôn có thể chỉ cần đọc tệp và sử dụng eval().

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

Tôi thấy điều này hữu ích trong khi kiểm tra đơn vị các tệp JS phía máy khách cho một hệ thống cũ.

Các tệp JS sẽ thiết lập rất nhiều biến toàn cục bên dưới windowmà không có bất kỳ require(...)module.exports câu lệnh câu lệnh nào (không có trình đóng gói mô-đun như Webpack hoặc Browserify có sẵn để loại bỏ các câu lệnh này).

Thay vì cấu trúc lại toàn bộ cơ sở mã, điều này cho phép chúng tôi tích hợp các thử nghiệm đơn vị trong JS phía máy khách của chúng tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.