node.js 모듈에서 내부(내보내기 아님) 기능에 액세스하고 테스트하는 방법은 무엇입니까?
nodejs(모카 또는 자스민을 사용하는 것이 좋습니다)에서 내부(즉, 내보내지 않음) 기능을 테스트하는 방법을 알아보려고 합니다.그리고 나는 전혀 모릅니다!
예를 들어, 다음과 같은 모듈이 있습니다.
function exported(i) {
return notExported(i) + 1;
}
function notExported(i) {
return i*2;
}
exports.exported = exported;
그리고 다음 테스트(모카):
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));
});
});
});
유닛 테스트 방법이 있습니까?notExported
노출될 의도가 없기 때문에 실제로 내보내지 않고 기능하는 것입니까?
리와이어 모듈이 정답입니다.
내보내지 않은 함수에 액세스하여 모카를 사용하여 테스트하기 위한 코드입니다.
어플.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');
var 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();
});
});
비결은 다음을 설정하는 것입니다.NODE_ENV
는 경변수 유것사한와환과 같은 입니다.test
그런 다음 조건부로 수출합니다.
만약 당신이 mocha를 글로벌하게 설치하지 않았다고 가정한다면, 당신의 앱 디렉터리의 루트에 다음을 포함하는 Makefile이 있을 수 있습니다.
REPORTER = dot
test:
@NODE_ENV=test ./node_modules/.bin/mocha \
--recursive --reporter $(REPORTER) --ui bbd
.PHONY: test
이 make 파일은 mocha를 실행하기 전에 NODE_ENV를 설정합니다.그런 다음 모카 테스트를 실행할 수 있습니다.make test
명령행에서.
이제 mocha 테스트가 실행 중일 때만 내보내지 않는 기능을 조건부로 내보낼 수 있습니다.
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;
다른 답변에서는 vm 모듈을 사용하여 파일을 평가할 것을 제안했지만, 이 방법은 작동하지 않고 내보내기가 정의되지 않았다는 오류가 발생합니다.
편집:
방법을 사용vm
예기치 않은 동작을 일으킬 수 있습니다(예:instanceof
이 일반적으로 에 더 이상 .require
) 저는 더 이상 아래 기술을 사용하지 않고 리와이어 모듈을 사용합니다.그것은 아주 잘 작동합니다.제 원래 대답은 이렇습니다.
로시의 대답을 자세히 설명하자면...
다소 진부한 느낌이 들지만 애플리케이션 모듈에 조건부 내보내기 없이 원하는 작업을 수행할 수 있는 간단한 "test_utils.js" 모듈을 작성했습니다.
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;};
모듈의 에는 몇 가 더 되어 있습니다.module
에 들어갈 context
위의 개체입니다. 하지만 이것이 작동하기 위해 필요한 최소 세트입니다.
다음은 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));
});
});
저는 의존성 없이 다른 접근 방식을 사용해 왔습니다.테스트할 모든 로컬 함수가 포함된 __testing 내보내기를 수행합니다. 이 값은 NODE_ENV에 따라 달라지므로 테스트에서만 액세스할 수 있습니다.
// file.ts
const localFunction = () => console.log('do something');
const localFunciton2 = () => console.log('do something else');
export const exportedFunction = () => {
localFunction();
localFunciton2();
}
export const __testing = (process.env.NODE_ENV === 'test') ? {
localFunction, localFunction2
} : void 0;
// file.test.ts
import { __testing, exportedFunction } from './file,ts'
const { localFunction, localFunction2 } = __testing!;
// Now you can test local functions
재스민과 함께 일하면서, 저는 재와이어를 기반으로 앤서니 메이필드가 제안한 해결책을 더 깊이 들어가려고 노력했습니다.
저는 다음 기능을 구현했습니다(주의: 아직 완전히 테스트되지는 않았지만 가능한 전략으로 공유되었습니다).
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);
}
}
이와 같은 기능을 사용하면 내보내지 않은 개체와 내보내지 않은 최상위 기능의 두 가지 방법을 모두 다음과 같이 감시할 수 있습니다.
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
그런 다음 다음과 같은 기대치를 설정할 수 있습니다.
expect(dbLoader.rewired.fs.readFileSync).toHaveBeenCalled();
expect(dbLoader.rewired.message).toHaveBeenCalledWith(POST_DESCRIPTION);
테스트 내에서 내부 기능을 테스트, 스파이 및 조롱할 수 있는 매우 간단한 방법을 찾았습니다.
다음과 같은 노드 모듈이 있다고 가정합니다.
mymodule.js:
------------
"use strict";
function myInternalFn() {
}
function myExportableFn() {
myInternalFn();
}
exports.myExportableFn = myExportableFn;
만약 우리가 지금 테스트하고 스파이하고 조롱하고 싶다면,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;
이제 테스트하고, 스파이하고, 모의할 수 있습니다.myInternalFn
▁you로 사용하는 모든 곳에서.testable.myInternalFn
생산 시에는 수출되지 않습니다.
기본적으로 소스 컨텍스트를 테스트 사례와 병합해야 합니다. 한 가지 방법은 테스트를 래핑하는 작은 도우미 기능을 사용하는 것입니다.
demo.js
const internalVar = 1;
demo.test.js
const importing = (sourceFile, tests) => eval(`${require('fs').readFileSync(sourceFile)};(${String(tests)})();`);
importing('./demo.js', () => {
it('should have context access', () => {
expect(internalVar).toBe(1);
});
});
vm 모듈을 사용하여 새 컨텍스트를 만들고 repl과 같은 방식으로 js 파일을 평가할 수 있습니다.그러면 당신은 그것이 선언하는 모든 것에 접근할 수 있습니다.
, 이 이법방는권않지를 수 은 권장되지 않습니다.rewire
@Antoine에 의해 제안된 것처럼, 당신은 항상 파일을 읽고 사용할 수 있습니다.eval()
.
var fs = require('fs');
const JsFileString = fs.readFileSync(fileAbsolutePath, 'utf-8');
eval(JsFileString);
레거시 시스템에 대한 클라이언트 측 JS 파일을 유닛 테스트하는 동안 이 기능이 유용하다는 것을 알게 되었습니다.
아래에 많은 글로벌 변수를 설정합니다.window
것도 없이require(...)
그리고.module.exports
문(Webpack 또는 Browserify와 같은 모듈 번들러를 사용하여 이러한 문을 제거할 수 없습니다.)
이를 통해 전체 코드베이스를 재팩터링하는 대신 클라이언트 측 JS에 유닛 테스트를 통합할 수 있었습니다.
eval
실제로는 자체적으로 작동하지 않습니다(최상위 기능 또는var
선언), let 또는 const로 선언된 최상위 변수를 eval로 현재 컨텍스트에 캡처할 수는 없지만, VM을 사용하여 현재 컨텍스트에서 실행하면 실행 후 모든 최상위 변수에 액세스할 수 있습니다.
eval("let local = 42;")
// local is undefined/undeclared here
const vm = require("vm")
vm.runInThisContext("let local = 42;");
// local is 42 here
...같은 이름을 공유하는 경우 VM이 시작될 때 현재 컨텍스트에서 이미 선언/정의된 모든 선언 또는 할당과 충돌할 수 있습니다.
여기 평범한 해결책이 있습니다.그러나 이렇게 하면 가져온 모듈/장치에 불필요한 코드가 약간 추가되며, 이러한 방식으로 장치 테스트를 실행하려면 테스트 제품군에서 각 파일을 직접 실행해야 합니다.모듈을 직접 실행하여 장치 테스트를 실행하는 것 외에는 다른 작업을 수행하는 것은 더 많은 코드 없이는 불가능합니다.
가져온 모듈에서 파일이 기본 모듈인지 확인하고, 있으면 테스트를 실행합니다.
const local = {
doMath() {return 2 + 2}
};
const local2 = 42;
if (require.main === module) {
require("./test/tests-for-this-file.js")({local, local2});
}
그런 다음 대상 모듈을 가져오는 테스트 파일/모듈에서 다음을 수행합니다.
module.exports = function(localsObject) {
// do tests with locals from target module
}
이제 타겟 모듈을 직접 실행합니다.node MODULEPATH
테스트를 실행합니다.
다음과 같은 조건부 내보내기를 시도해 보십시오.
참고: 이 솔루션을 사용하면 Jest와의 리와이어 종속성 문제로 인해 리와이어를 통해 테스트된 기능이 테스트 범위에 포함되지 않으므로 테스트 범위 보고서에 해당 기능이 포함될 수 있습니다. - @robros0606
a.js
module.exports = {
foo,
foo1: process.env['NODE_DEV'] == 'TEST123' && foo1
}
a.test.js
process.env['NODE_DEV'] = 'TEST123'
const { foo1, foo } = require('./a')
이렇게 하면 테스트된 기능도 테스트 범위에 포함됩니다.
언급URL : https://stackoverflow.com/questions/14874208/how-to-access-and-test-an-internal-non-exports-function-in-a-node-js-module
'source' 카테고리의 다른 글
타이머로 트리거된 Azure 기능을 로컬로 한 번 실행하는 가장 간단한 방법은 무엇입니까? (0) | 2023.05.16 |
---|---|
.NET에서 'for'와 'for ach' 중 어떤 루프가 더 빨리 실행됩니까? (0) | 2023.05.16 |
Pydoc으로 문서를 작성하려면 어떻게 해야 합니까? (0) | 2023.05.16 |
Azure 지역을 모두 나열할 수 있는 API가 있습니까? (0) | 2023.05.16 |
파일, 어셈블리 또는 종속성 중 하나를 로드할 수 없습니다. (0) | 2023.05.16 |