[javascript] 다른 모듈이 필요한 Node.js 모듈을 단위 테스트하는 방법과 전역 요구 기능을 조롱하는 방법은 무엇입니까?

이것은 내 문제의 요점을 보여주는 사소한 예입니다.

var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.doComplexStuff();
}

module.exports = underTest;

이 코드에 대한 단위 테스트를 작성하려고합니다. 함수를 완전히 innerLib조롱하지 않고 요구 사항을 어떻게 조롱 할 수 require있습니까?

그래서 이것은 전 세계를 조롱하려고 시도 require하고 그렇게 할 수조차 없다는 것을 알게되었습니다.

var path = require('path'),
    vm = require('vm'),
    fs = require('fs'),
    indexPath = path.join(__dirname, './underTest');

var globalRequire = require;

require = function(name) {
    console.log('require: ' + name);
    switch(name) {
        case 'connect':
        case indexPath:
            return globalRequire(name);
            break;
    }
};

문제는 파일 require내부 의 함수 underTest.js가 실제로 조롱되지 않았다는 것입니다. 여전히 전역 require기능을 가리 킵니다 . 따라서 내가 조롱하고있는 require동일한 파일 내 에서만 함수를 조롱 할 수있는 것처럼 보입니다 . require로컬 복사본을 재정의 한 후에도 전역 을 사용하여 아무것도 포함하면 필요한 파일은 여전히 글로벌 require레퍼런스.



답변

당신은 지금 할 수 있습니다!

테스트하는 동안 모듈 내부의 전역 요구를 무시하는 proxyquire 를 게시 했습니다.

이는 필요한 모듈에 대한 모의를 주입하기 위해 코드변경할 필요가 없음을 의미합니다 .

Proxyquire는 매우 간단한 API를 사용하여 테스트하려는 모듈을 해결하고 필요한 모듈에 대한 모의 / 스텁을 따라 한 번의 간단한 단계로 전달할 수 있습니다.

@Raynos는 전통적으로 그 목표를 달성하거나 대신 상향식 개발을하기 위해 이상적인 솔루션이 아닌 솔루션을 사용해야했습니다.

이것이 바로 프록시 요구 사항을 만든 주된 이유입니다. 번거 로움없이 하향식 테스트 중심 개발을 허용합니다.

필요에 맞는지 측정하기 위해 설명서와 예제를 살펴보십시오.


답변

이 경우 더 좋은 옵션은 반환되는 모듈의 메서드를 조롱하는 것입니다.

더 좋든 나쁘 든 대부분의 node.js 모듈은 싱글 톤입니다. 동일한 모듈을 필요로하는 두 개의 코드는 해당 모듈에 대한 동일한 참조를 얻습니다.

이것을 활용하고 sinon 과 같은 것을 사용 하여 필요한 항목을 조롱 할 수 있습니다. 모카 테스트는 다음과 같습니다.

// in your testfile
var innerLib  = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon     = require('sinon');

describe("underTest", function() {
  it("does something", function() {
    sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
      // whatever you would like innerLib.toCrazyCrap to do under test
    });

    underTest();

    sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion

    innerLib.toCrazyCrap.restore(); // restore original functionality
  });
});

Sinon은 어설 션을 만들기 위해 chai와통합되어 있으며 , 간첩과 스텁을보다 쉽게 청소할 수 있도록 sinon과 mocha통합 하는 모듈을 작성했습니다 (테스트 오염 방지).

underTest는 함수 만 반환하므로 동일한 방법으로 underTest를 조롱 할 수 없습니다.

또 다른 옵션은 Jest mock을 사용하는 것입니다. 그들의 페이지 에 후속


답변

mock-require 사용 합니다 . require테스트 할 모듈 전에 모의 객체를 정의해야합니다 .


답변

조롱 require은 나에게 심한 해킹처럼 느껴집니다. 나는 개인적으로 그것을 피하고 코드를 리팩터링하여 더 테스트 가능하게 만들려고합니다. 종속성을 처리하는 다양한 방법이 있습니다.

1) 의존성을 인수로 전달

function underTest(innerLib) {
    return innerLib.doComplexStuff();
}

이렇게하면 코드를 보편적으로 테스트 할 수 있습니다. 단점은 종속성을 전달해야하므로 코드가 더 복잡해 보일 수 있다는 것입니다.

2) 모듈을 클래스로 구현 한 다음 클래스 메소드 / 속성을 사용하여 종속성을 얻습니다.

(이것은 클래스 사용이 합리적이지 않지만 아이디어를 전달하는 고안된 예입니다.) (ES6 예)

const innerLib = require('./path/to/innerLib')

class underTestClass {
    getInnerLib () {
        return innerLib
    }

    underTestMethod () {
        return this.getInnerLib().doComplexStuff()
    }
}

이제 getInnerLib코드를 테스트하는 방법을 쉽게 스텁 할 수 있습니다 . 코드가 더 장황 해지지 만 테스트하기도 더 쉽습니다.


답변

jest를 사용해 본 적이 있다면 아마도 jest의 모의 기능에 익숙 할 것입니다.

“jest.mock (…)”을 사용하면 코드의 require 문에서 발생할 문자열을 지정하고 해당 문자열을 사용하여 모듈이 필요할 때마다 mock-object가 반환됩니다.

예를 들어

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})

“firebase-admin”의 모든 가져 오기 / 요구 사항을 “공장”기능에서 반환 한 객체로 완전히 대체합니다.

jest를 사용하면 jest가 실행하는 모든 모듈 주위에 런타임을 생성하고 모듈에 “hooked”버전의 require를 주입하기 때문에 jest를 사용할 때 그렇게 할 수 있지만 jest 없이는이를 수행 할 수 없습니다.

나는 mock-require로 이것을 달성하려고 노력했지만 내 소스의 중첩 된 레벨에서는 작동하지 않았다. github에서 다음 문제를 살펴보십시오. mock-require가 항상 Mocha와 함께 호출되는 것은 아닙니다 .

이 문제를 해결하기 위해 원하는 것을 달성하는 데 사용할 수있는 두 개의 npm 모듈을 만들었습니다.

하나의 babel-plugin과 모듈 mocker가 필요합니다.

.babelrc에서 다음 옵션과 함께 babel-plugin-mock-require 플러그인을 사용하십시오.

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...

테스트 파일에서 다음과 같이 jestlike-mock 모듈을 사용하십시오.

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...

jestlike-mock모듈은 여전히 ​​초보적이며 많은 문서가 없지만 코드는 많지 않습니다. 보다 완전한 기능 세트에 대한 모든 PR에 감사드립니다. 목표는 전체 “jest.mock”기능을 재생성하는 것입니다.

jest가이를 구현하는 방법을보기 위해 “jest-runtime”패키지에서 코드를 찾을 수 있습니다. 예를 들어 https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 를 참조 하십시오 . 여기에서 모듈의 “자동”을 생성합니다.

희망이 도움이됩니다;)


답변

당신은 할 수 없습니다. 가장 낮은 모듈을 먼저 테스트하고 모듈이 필요한 높은 수준의 모듈을 나중에 테스트 할 수 있도록 장치 테스트 스위트를 구축해야합니다.

또한 타사 코드 및 node.js 자체가 잘 테스트되었다고 가정해야합니다.

나는 당신이 조롱 프레임 워크가 가까운 미래에 도착하는 것을 보게 될 것이라고 가정합니다. global.require

실제로 모형을 주입해야하는 경우 모듈 식 범위를 노출하도록 코드를 변경할 수 있습니다.

// underTest.js
var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.toCrazyCrap();
}

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}

이것은 .__moduleAPI에 노출 되며 모든 코드는 위험에 따라 모듈 식 범위에 액세스 할 수 있습니다.


답변

조롱 라이브러리 를 사용할 수 있습니다 :

describe 'UnderTest', ->
  before ->
    mockery.enable( warnOnUnregistered: false )
    mockery.registerMock('./path/to/innerLib', { doComplexStuff: -> 'Complex result' })
    @underTest = require('./path/to/underTest')

  it 'should compute complex value', ->
    expect(@underTest()).to.eq 'Complex result'