[javascript] ES6 모듈의 수입을 조롱하는 방법?

다음과 같은 ES6 모듈이 있습니다.

network.js

export function getDataFromServer() {
  return ...
}

widget.js

import { getDataFromServer } from 'network.js';

export class Widget() {
  constructor() {
    getDataFromServer("dataForWidget")
    .then(data => this.render(data));
  }

  render() {
    ...
  }
}

의 모의 인스턴스로 위젯을 테스트하는 방법을 찾고 getDataFromServer있습니다. <script>Karma와 같이 ES6 모듈 대신 별도 의 s를 사용하면 다음 과 같이 테스트를 작성할 수 있습니다.

describe("widget", function() {
  it("should do stuff", function() {
    let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData")
    let widget = new Widget();
    expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
    expect(otherStuff).toHaveHappened();
  });
});

그러나 브라우저 외부에서 개별적으로 ES6 모듈을 테스트하는 경우 (Mocha + babel과 같은) 다음과 같이 작성합니다.

import { Widget } from 'widget.js';

describe("widget", function() {
  it("should do stuff", function() {
    let getDataFromServer = spyOn(?????) // How to mock?
    .andReturn("mockData")
    let widget = new Widget();
    expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
    expect(otherStuff).toHaveHappened();
  });
});

좋아, 그러나 지금 getDataFromServer은 사용할 수 window없으며 (아무도 없다 window), 나는 물건을 widget.js자신의 범위에 직접 주입하는 방법을 모른다 .

여기서 어디로 가야합니까?

  1. 의 범위에 액세스 widget.js하거나 가져 오기를 내 코드로 바꾸는 방법이 있습니까?
  2. 그렇지 않다면 어떻게 Widget테스트 할 수 있습니까?

내가 고려한 것들 :

ㅏ. 수동 의존성 주입.

모든 가져 오기를 제거 widget.js하고 발신자가 뎁스를 제공 할 것으로 예상합니다.

export class Widget() {
  constructor(deps) {
    deps.getDataFromServer("dataForWidget")
    .then(data => this.render(data));
  }
}

위젯의 공용 인터페이스를 엉망으로 만들고 구현 세부 정보를 노출하는 것이 매우 불편합니다. 안돼


비. 수입품을 조롱 할 수 있도록 노출하십시오.

다음과 같은 것 :

import { getDataFromServer } from 'network.js';

export let deps = {
  getDataFromServer
};

export class Widget() {
  constructor() {
    deps.getDataFromServer("dataForWidget")
    .then(data => this.render(data));
  }
}

그때:

import { Widget, deps } from 'widget.js';

describe("widget", function() {
  it("should do stuff", function() {
    let getDataFromServer = spyOn(deps.getDataFromServer)  // !
      .andReturn("mockData");
    let widget = new Widget();
    expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
    expect(otherStuff).toHaveHappened();
  });
});

이것은 덜 침습적이지만 각 모듈에 대해 많은 상용구를 작성해야하며 항상 getDataFromServer대신 사용하는 위험 deps.getDataFromServer이 있습니다. 나는 그것에 대해 불안하지만 그것이 지금까지 나의 최고의 아이디어입니다.



답변

import * as obj테스트 내 에서 스타일을 사용하기 시작했습니다 . 모듈에서 모든 내보내기를 개체의 속성으로 가져온 다음 조롱 할 수 있습니다. 나는 rewire 또는 proxyquire 또는 유사한 기술을 사용하는 것보다 훨씬 깨끗하다는 것을 알았습니다. 예를 들어 Redux 작업을 조롱해야 할 때이 작업을 가장 자주 수행했습니다. 위의 예제에 사용할 수있는 내용은 다음과 같습니다.

import * as network from 'network.js';

describe("widget", function() {
  it("should do stuff", function() {
    let getDataFromServer = spyOn(network, "getDataFromServer").andReturn("mockData")
    let widget = new Widget();
    expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
    expect(otherStuff).toHaveHappened();
  });
});

함수는 기본 수출 될 일 경우, import * as network from './network'생산 것입니다 {default: getDataFromServer}그리고 당신은 network.default 조롱 수 있습니다.


답변

@carpeliam은 정확하지만 모듈의 함수를 감시하고 해당 함수를 호출하는 해당 모듈의 다른 함수를 사용하려면 내보내기 네임 스페이스의 일부로 해당 함수를 호출해야합니다. 그렇지 않으면 스파이가 사용되지 않습니다.

잘못된 예 :

// mymodule.js

export function myfunc2() {return 2;}
export function myfunc1() {return myfunc2();}

// tests.js
import * as mymodule

describe('tests', () => {
    beforeEach(() => {
        spyOn(mymodule, 'myfunc2').and.returnValue = 3;
    });

    it('calls myfunc2', () => {
        let out = mymodule.myfunc1();
        // out will still be 2
    });
});

올바른 예 :

export function myfunc2() {return 2;}
export function myfunc1() {return exports.myfunc2();}

// tests.js
import * as mymodule

describe('tests', () => {
    beforeEach(() => {
        spyOn(mymodule, 'myfunc2').and.returnValue = 3;
    });

    it('calls myfunc2', () => {
        let out = mymodule.myfunc1();
        // out will be 3 which is what you expect
    });
});


답변

명시 적 종속성 주입에 대해 알기 위해 원래 클래스가 필요없이 Typescript 클래스 가져 오기의 런타임 조롱 문제를 해결하려는 라이브러리를 구현했습니다.

라이브러리는 import * as구문을 사용한 다음 원래 내 보낸 객체를 스텁 클래스로 바꿉니다. 유형 안전성을 유지하므로 해당 테스트를 업데이트하지 않고 메소드 이름을 업데이트 한 경우 컴파일 타임에 테스트가 중단됩니다.

이 라이브러리는 여기에서 찾을 수 있습니다 : ts-mock-imports .


답변

@ vdloo의 대답으로 올바른 방향으로 향했지만 공통 파일 “exports”와 ES6 모듈 “export”키워드를 같은 파일에 함께 사용하면 나에게 효과가 없었습니다 (webpack v2 이상이 불평합니다). 대신, 이름이 지정된 개별 모듈 내보내기를 모두 래핑하고 테스트 파일에서 기본 내보내기를 가져 오는 기본 (이름이 지정된 변수) 내보내기를 사용하고 있습니다. mocha / sinon과 함께 다음 내보내기 설정을 사용하고 있으며 스터 빙은 다시 와이어 링 등이 필요하지 않고 잘 작동합니다.

// MyModule.js
let MyModule;

export function myfunc2() { return 2; }
export function myfunc1() { return MyModule.myfunc2(); }

export default MyModule = {
  myfunc1,
  myfunc2
}

// tests.js
import MyModule from './MyModule'

describe('MyModule', () => {
  const sandbox = sinon.sandbox.create();
  beforeEach(() => {
    sandbox.stub(MyModule, 'myfunc2').returns(4);
  });
  afterEach(() => {
    sandbox.restore();
  });
  it('myfunc1 is a proxy for myfunc2', () => {
    expect(MyModule.myfunc1()).to.eql(4);
  });
});


답변

이 구문이 작동하는 것을 발견했습니다.

내 모듈 :

// mymod.js
import shortid from 'shortid';

const myfunc = () => shortid();
export default myfunc;

내 모듈의 테스트 코드 :

// mymod.test.js
import myfunc from './mymod';
import shortid from 'shortid';

jest.mock('shortid');

describe('mocks shortid', () => {
  it('works', () => {
    shortid.mockImplementation(() => 1);
    expect(myfunc()).toEqual(1);
  });
});

문서를 참조하십시오 .


답변

나는 그것을 직접 시도하지는 않았지만 조롱 이 효과가 있다고 생각 합니다. 실제 모듈을 제공 한 모의로 대체 할 수 있습니다. 아래는 작동 방식에 대한 아이디어를 제공하는 예입니다.

mockery.enable();
var networkMock = {
    getDataFromServer: function () { /* your mock code */ }
};
mockery.registerMock('network.js', networkMock);

import { Widget } from 'widget.js';
// This widget will have imported the `networkMock` instead of the real 'network.js'

mockery.deregisterMock('network.js');
mockery.disable();

mockery더 이상 유지되지 않는 것 같고 Node.js에서만 작동한다고 생각하지만 덜 조롱하지 않으면 모의하기 어려운 모듈을 조롱하는 깔끔한 솔루션입니다.


답변