programing

다른 모듈이 필요한 Node.js 모듈을 유닛 테스트하는 방법과 글로벌 요구 기능을 조롱하는 방법은 무엇입니까?

subpage 2023. 8. 2. 09:04
반응형

다른 모듈이 필요한 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에는 테스트하려는 모듈을 해결하고 필요한 모듈에 대한 모크/스텁을 한 번의 간단한 단계로 전달할 수 있는 매우 간단한 api가 있습니다.

@Raynos는 전통적으로 이를 달성하거나 상향식 개발을 수행하기 위해 그다지 이상적이지 않은 솔루션에 의존해야 했다는 것이 맞습니다.

이것이 바로 제가 프록시 퀘이어를 만든 주된 이유입니다. 번거로움 없이 하향식 테스트 기반 개발이 가능하도록 하기 위함입니다.

설명서와 예제를 보고 필요에 적합한지 판단합니다.

이 경우 반환되는 모듈의 메서드를 모의 실행하는 것이 더 좋습니다.

좋든 나쁘든 대부분의 node.js 모듈은 싱글톤이며, 동일한 모듈이 필요한() 두 코드 조각은 해당 모듈에 대해 동일한 참조를 가져옵니다.

이를 활용하여 시논과 같은 것을 사용하여 필요한 항목을 모의 분석할 수 있습니다. 모카 테스트는 다음과 같습니다.

// 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를 통합하여 (시험 오염을 피하기 위해) 더 쉬운 스파이/스텁 정리를 할 수 있는 모듈을 작성했습니다.

테스트에서는 함수만 반환되므로 테스트에서는 동일한 방법으로 조롱할 수 없습니다.

또 다른 방법은 Jest mocks를 사용하는 것입니다.그들의 페이지에 대한 후속 조치

는 모의 요구를 사용합니다.당신이 당신의 모크를 정의하기 전에 확실히 하라.require테스트할 모듈.

호기심 많은 사람들을 위해 모듈을 조롱하는 간단한 코드

당신이 조작하는 부분에 주목하세요.require.cache 참고 사항 및고require.resolve이것이 비밀 소스이기 때문에 방법.

class MockModules {  
  constructor() {
    this._resolvedPaths = {} 
  }
  add({ path, mock }) {
    const resolvedPath = require.resolve(path)
    this._resolvedPaths[resolvedPath] = true
    require.cache[resolvedPath] = {
      id: resolvedPath,
      file: resolvedPath,
      loaded: true,
      exports: mock
    }
  }
  clear(path) {
    const resolvedPath = require.resolve(path)
    delete this._resolvedPaths[resolvedPath]
    delete require.cache[resolvedPath]
  }
  clearAll() {
    Object.keys(this._resolvedPaths).forEach(resolvedPath =>
      delete require.cache[resolvedPath]
    )
    this._resolvedPaths = {}
  }
}

다음과 같은 용도:

describe('#someModuleUsingTheThing', () => {
  const mockModules = new MockModules()
  beforeAll(() => {
    mockModules.add({
      // use the same require path as you normally would
      path: '../theThing',
      // mock return an object with "theThingMethod"
      mock: {
        theThingMethod: () => true
      }
    })
  })
  afterAll(() => {
    mockModules.clearAll()
  })
  it('should do the thing', async () => {
    const someModuleUsingTheThing = require('./someModuleUsingTheThing')
    expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
  })
})

하지만... just에는 이 기능이 내장되어 있습니다. 테스트용 프레임워크를 테스트용으로 롤오버하는 것이 좋습니다.

롱조require저한테는 끔찍한 해킹처럼 느껴집니다.저는 개인적으로 그것을 피하고 코드를 더 테스트할 수 있도록 리팩터링할 것입니다.종속성을 처리하는 방법은 다양합니다.

종속성을 인수로 넘기다.

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

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

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

(이것은 수업 사용이 합리적이지 않지만 아이디어를 전달하는 인위적인 예입니다.) (ES6 예)

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

class underTestClass {
    getInnerLib () {
        return innerLib
    }

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

스텁할 수 .getInnerLib코드를 테스트하는 방법입니다.코드가 더 장황해지지만 테스트하기도 더 쉬워집니다.

만약 여러분이 농담을 사용해본 적이 있다면, 여러분은 아마도 농담의 모의 기능에 익숙할 것입니다.

"jest.mock"(...) 사용)" 코드의 require-statement에서 발생할 문자열을 간단히 지정할 수 있으며 모듈이 해당 문자열을 사용하여 필요할 때마다 mock-object가 대신 반환됩니다.

예를들면

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

"firebase-admin"의 모든 가져오기/내보내기를 해당 "공장" 함수에서 반환한 개체로 완전히 대체합니다.

just가 실행되는 모든 모듈 주위에 런타임을 생성하고 "후크된" 버전의 require를 모듈에 주입하기 때문에 just를 사용할 때 이 작업을 수행할 수 있지만, just 없이는 수행할 수 없습니다.

나는 모의 요구로 이것을 달성하려고 노력했지만 나에게는 그것이 내 소스의 중첩된 수준에서 작동하지 않았습니다.github에서 다음 문제를 살펴보세요: mock-require는 항상 Mocha와 함께 호출되지 않습니다.

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

바벨 플러그인 하나와 모듈 코커가 필요합니다.

.babelrc에서 babel-plugin-mock-require 플러그인을 다음 옵션과 함께 사용합니다.

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

그리고 테스트 파일에서 다음과 같이 justlike-tunnel 모듈을 사용합니다.

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

jestlike-mock모듈은 여전히 매우 기초적이고 많은 문서를 가지고 있지 않지만 코드도 많지 않습니다.더 완벽한 피처 세트를 위한 PR에 감사드립니다.목표는 전체 "jest.mock" 기능을 다시 만드는 것입니다.

just가 "just-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());
}

이로 인해 노출될 수.__module모든 코드가 위험을 무릅쓰고 모듈식 스코프에 액세스할 수 있습니다.

모의 라이브러리를 사용할 수 있습니다.

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'

저는 간단한 팩토리를 사용합니다. 는 모든 종속성을 가진 함수를 호출하는 함수를 반환합니다.

/**
 * fnFactory
 * Returns a function that calls a function with all of its dependencies.
*/

"use strict";

const fnFactory = ({ target, dependencies }) => () => target(...dependencies);

module.exports = fnFactory;

다음 기능을 테스트하려고 합니다.

/*
 * underTest
*/

"use strict";

const underTest = ( innerLib, millions ) => innerLib.doComplexStuff(millions);

module.exports = underTest;

다음과 같이 테스트(Jest 사용)를 설정합니다.

"use strict";

const fnFactory = require("./fnFactory");
const _underTest = require("./underTest");

test("fnFactory can mock a function by returng a function that calls a function with all its dependencies", () => {
    const fake = millions => `Didn't do anything with ${millions} million dollars!`;
    const underTest = fnFactory({ target: _underTest, dependencies: [{ doComplexStuff: fake  }, 10] });
    expect(underTest()).toBe("Didn't do anything with 10 million dollars!");
});

테스트 결과 보기

생산 코드에서 아래와 같이 수동으로 호출자의 종속성을 주입합니다.

/**
 * main
 * Entry point for the real application.
*/

"use strict";

const underTest = require("./underTest");
const innerLib = require("./innerLib");

underTest(innerLib, 10);

저는 제가 작성하는 대부분의 모듈의 범위를 한 가지로 제한하는 경향이 있으며, 이로 인해 모듈을 테스트하고 프로젝트에 통합할 때 고려해야 하는 종속성의 수가 줄어듭니다.

그리고 이것이 의존성을 다루는 저의 접근법입니다.

언급URL : https://stackoverflow.com/questions/5747035/how-to-unit-test-a-node-js-module-that-requires-other-modules-and-how-to-mock-th

반응형