모듈이 모의 해제되었을 때 Jest에서 가져온 명명 된 함수를 모의하는 방법


120

Jest에서 테스트하려는 다음 모듈이 있습니다.

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

위와 같이, 그것은 중요한 일부라는 이름의 함수와 수출 testFn용도 otherFn.

나는 내 단위 테스트를 쓰고 있어요 때 농담으로 testFn, 나는 조롱 할 otherFn에 내가 오류를 원하지 않기 때문에 기능을 otherFn내 단위 테스트에 영향을 testFn. 내 문제는 최선의 방법이 확실하지 않다는 것입니다.

// myModule.test.js
jest.unmock('myModule');

import { testFn, otherFn } from 'myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    // I want to mock "otherFn" here but can't reassign
    // a.k.a. can't do otherFn = jest.fn()
  });
});

모든 도움 / 통찰을 주시면 감사하겠습니다.


7
나는 이것을하지 않을 것이다. 조롱은 일반적으로 어쨌든하고 싶은 일이 아닙니다. 그리고 서버 호출 등으로 인해 무언가를 조롱해야하는 경우 otherFn별도의 모듈로 추출 하여 조롱해야합니다.
kentcdodds

2
또한 @jrubins가 사용하는 것과 동일한 접근 방식으로 테스트하고 있습니다. function A누가 호출하는지 테스트 동작을 function B수행하지만 실제 구현을 실행하고 싶지는 않습니다.에서 function B구현 된 논리를 테스트하고 싶기 때문입니다function A
jplaza

48
@kentcdodds, "조롱은 일반적으로 어차피하고 싶은 일이 아닙니다."라는 의미를 명확히 해주실 수 있습니까? 조롱은 아마도 (적어도 일부) 좋은 이유 때문에 자주 사용되는 것이므로 상당히 광범위한 (지나치게 광범위한?) 진술인 것 같습니다. 그래서, 당신은 왜 조롱이 여기서 좋지 않을 수 있는지를 언급하고 있습니까, 아니면 정말로 일반적으로 의미합니까?
Andrew Willems

2
종종 조롱은 구현 세부 사항을 테스트하는 것입니다. 특히이 수준에서는 테스트가 작동한다는 사실보다 훨씬 더 많은 유효성을 검증하지 않는 테스트로 이어집니다 (코드가 작동하지 않음).
kentcdodds

3
기록을 위해 몇 년 전이 질문을 작성한 이후로 나는 얼마나 많이 조롱하고 싶은지에 대한 내 조율을 변경했습니다 (더 이상 이렇게 조롱하지 마십시오). 요즘 나는 @kentcdodds와 그의 테스트 철학에 매우 동의하지만 (그의 블로그와 @testing-library/react다른 Reacters 에게 적극 추천합니다 ) 이것이 논쟁의 여지가있는 주제라는 것을 알고 있습니다.
Jon Rubins

답변:


113

jest.requireActual()내부 사용jest.mock()

jest.requireActual(moduleName)

모의 구현을 수신해야하는지 여부에 대한 모든 검사를 무시하고 모의 대신 실제 모듈을 반환합니다.

반환 된 객체 내에서 필요하고 확산되는이 간결한 사용법을 선호합니다.

// myModule.test.js

jest.mock('./myModule.js', () => (
  {
    ...(jest.requireActual('./myModule.js')),
    otherFn: jest.fn()
  }
))

import { otherFn } from './myModule.js'

describe('test category', () => {
  it('tests something about otherFn', () => {
    otherFn.mockReturnValue('foo')
    expect(otherFn()).toBe('foo')
  })
})

이 방법은 Jest의 Manual Mocks 문서 ( Examples 의 끝 부분에 있음 ) 에서도 참조됩니다 .

수동 모의와 실제 구현이 동기화 상태를 유지하려면 jest.requireActual(moduleName)수동 모의에서 사용하는 실제 모듈을 요구하고 내보내기 전에 모의 함수로 수정하는 것이 유용 할 수 있습니다 .


4
Fwiw, return문 을 제거 하고 화살표 함수 본문을 괄호로 묶어 더 간결하게 만들 수 있습니다 . jest.mock('./myModule', () => ({ ...jest.requireActual('./myModule'), otherFn: () => {}}))
Nick F

2
이것은 훌륭하게 작동했습니다! 이것은 받아 들여진 대답이어야합니다.
JRJurman

2
...jest.requireActual내가 가지고 있기 때문에 제대로 나를 위해없는 일을했다 경로 앨리어싱과 하나 .. 작품 바벨을 사용 ...require.requireActual또는 경로에서 앨리어싱을 제거한 후
Tzahi 레

1
otherFun이 경우 호출 된 것을 어떻게 테스트 하시겠습니까? 가정otherFn: jest.fn()
Stevula

1
@Stevula 실제 사용 예를 보여주기 위해 답변을 업데이트했습니다. 내가 보여 mockReturnValue더 조롱 버전이 아닌 원래의 부르심을 받고 있음을 입증에 방법을,하지만 당신은 정말 그냥이 반환 값에 대한 주장없이 호출되었는지 여부를 확인하려는 경우, 당신은 농담 정규 사용할 수 있습니다 .toHaveBeenCalled().
gfullam

37
import m from '../myModule';

나를 위해 작동하지 않으며 다음을 사용했습니다.

import * as m from '../myModule';

m.otherFn = jest.fn();

5
다른 테스트를 방해하지 않도록 테스트 후 otherFn의 원래 기능을 어떻게 복원 하시겠습니까?
Aequitas 2017

1
매 테스트 후 모의를 지우도록 농담을 구성 할 수 있다고 생각합니까? 문서에서 : "clearMocks 구성 옵션은 테스트 사이에 자동으로 모의를 지우는 데 사용할 수 있습니다." clearkMocks: truejest package.json 구성을 설정할 수 있습니다 . facebook.github.io/jest/docs/en/mock-function-api.html
Cole

2
이것이 전역 상태를 변경하는 문제라면 항상 어떤 종류의 테스트 변수 안에 원래의 기능을 저장하고 테스트 후에 다시 가져올 수 있습니다
bobu

1
const 원본; beforeAll (() => {original = m.otherFn; m.otherFn = jest.fn ();}) afterAll (() => {m.otherFn = original;}) 작동하지만 테스트하지 않았습니다. it
bobu

27

이 파티에 늦는 것 같지만 가능합니다.

testFnotherFn 모듈을 사용하여 호출 하면 됩니다.

경우 testFn사용하는 모듈이 호출 otherFn다음에 대한 모듈 수출 otherFn조롱 할 수 및 testFn모의를 호출합니다.


다음은 작동하는 예입니다.

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    const mock = jest.spyOn(myModule, 'otherFn');  // spy on otherFn
    mock.mockReturnValue('mocked value');  // mock the return value

    expect(myModule.testFn()).toBe('mocked value');  // SUCCESS

    mock.mockRestore();  // restore otherFn
  });
});

3
이것은 본질적으로 페이스 북에서 사용되는 접근 방식의 ES6 버전이며이 포스트 의 중간에 페이스 북 개발자가 설명했습니다 .
Brian Adams

myModule을 자체로 가져 오는 대신exports.otherFn()
andrhamm

3
@andrhamm exports은 ES6에 존재하지 않습니다. exports.otherFn()ES6이 이전 모듈 구문으로 컴파일되고 있기 때문에 호출 은 현재 작동하지만 ES6이 기본적으로 지원되면 중단됩니다.
Brian Adams

지금이 정확한 문제가 있고 이전에이 문제가 발생했다고 확신합니다. 나무가 흔들리는 것을 돕기 위해 exports. <methodname>의 부하를 제거해야했고 많은 테스트를 망쳤습니다. 이게 효과가 있는지 볼게요.하지만 너무 엉망인 것 같습니다. 나는이 문제를 여러 번 보았고 다른 답변이 말했듯이 babel-plugin- rewire 또는 더 나은 npmjs.com/package/rewiremock 과 같은 것들이 위의 작업을 수행 할 수 있다고 확신합니다.
Astridax

반환 값을 모의하는 대신 던지기를 모의하는 것이 가능합니까? 편집 : 당신은 할 수 있습니다, 여기에 방법이 있습니다 stackoverflow.com/a/50656680/2548010
Big Money

10

트랜스 파일 된 코드는 babel otherFn()이 참조 하는 바인딩을 검색하는 것을 허용하지 않습니다 . 함수 expession을 사용하면 mocking을 얻을 수 있습니다 otherFn().

// myModule.js
exports.otherFn = () => {
  console.log('do something');
}

exports.testFn = () => {
  exports.otherFn();

  // do other things
}

 

// myModule.test.js
import m from '../myModule';

m.otherFn = jest.fn();

그러나 @kentcdodds가 이전 주석에서 언급했듯이, 아마도 mock을 원하지 않을 것입니다 otherFn(). 오히려 새로운 사양을 작성하고 otherFn()필요한 호출을 모의하십시오.

예를 들어 otherFn()http 요청을하는 경우 ...

// myModule.js
exports.otherFn = () => {
  http.get('http://some-api.com', (res) => {
    // handle stuff
  });
};

여기서 모의 http.get구현을 기반으로 어설 션 을 모의 하고 업데이트 할 수 있습니다.

// myModule.test.js
jest.mock('http', () => ({
  get: jest.fn(() => {
    console.log('test');
  }),
}));

1
otherFn과 testFn이 다른 여러 모듈에서 사용된다면 어떻게 될까요? 두 모듈을 사용하는 모든 테스트 파일에서 http mock을 설정해야합니까? 또한 testFn에 대한 테스트가 이미있는 경우 testFn을 사용하는 모듈에서 http 대신 testFn을 직접 스텁하지 않는 이유는 무엇입니까?
rickmed

1
따라서 otherFn가 깨지면 해당 테스트에 의존하는 모든 테스트가 실패합니다. 또한 otherFn내부에 5 개의 if가 있으면 testFn모든 하위 케이스에 대해 제대로 작동하는지 테스트해야 할 수도 있습니다 . 지금 테스트 할 코드 경로가 훨씬 더 많습니다.
Totty.js 2010 년

7

나는 이것이 오래 전에 요청되었다는 것을 알고 있지만, 바로이 상황에 부딪 쳤고 마침내 작동 할 해결책을 찾았습니다. 그래서 여기서 나눌 거라고 생각 했어요.

모듈의 경우 :

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

다음으로 변경할 수 있습니다.

// myModule.js

export const otherFn = () => {
  console.log('do something');
}

export const testFn = () => {
  otherFn();

  // do other things
}

함수 대신 상수로 내보내기. 나는 문제가 JavaScript의 호이 스팅과 관련이 있다고 믿고 사용 const하면 그 동작 을 방지합니다.

그런 다음 테스트에서 다음과 같은 것을 가질 수 있습니다.

import * as myModule from 'myModule';


describe('...', () => {
  jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return');

  // or

  myModule.otherFn = jest.fn(() => {
    // your mock implementation
  });
});

이제 모의가 평소 예상대로 작동합니다.


2

여기에서 찾은 답변을 혼합하여 문제를 해결했습니다.

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});

0

여기의 첫 번째 답변 외에도 babel-plugin-rewire 를 사용하여 가져온 명명 된 함수를 모의 할 수도 있습니다 . 이름이 지정된 함수 재배 선에 대한 섹션을 표면적으로 확인할 수 있습니다 .

여기서 상황에 대한 즉각적인 이점 중 하나는 함수에서 다른 함수를 호출하는 방법을 변경할 필요가 없다는 것입니다.


babel-plugin-rewire를 node.js와 함께 작동하도록 구성하는 방법은 무엇입니까?
Timur Gilauri

0

Brian Adams의 답변을 바탕으로 TypeScript에서 동일한 접근 방식을 사용할 수있었습니다. 또한 jest.doMock ()을 사용 하면 테스트 파일의 일부 특정 테스트에서만 모듈 함수를 모의 처리하고 각각에 대한 개별 모의 구현을 제공 할 수 있습니다.

src / module.ts

import * as module from './module';

function foo(): string {
  return `foo${module.bar()}`;
}

function bar(): string {
  return 'bar';
}

export { foo, bar };

test / module.test.ts

import { mockModulePartially } from './helpers';

import * as module from '../src/module';

const { foo } = module;

describe('test suite', () => {
  beforeEach(function() {
    jest.resetModules();
  });

  it('do not mock bar 1', async() => {
    expect(foo()).toEqual('foobar');
  });

  it('mock bar', async() => {
    mockModulePartially('../src/module', () => ({
      bar: jest.fn().mockImplementation(() => 'BAR')
    }));
    const module = await import('../src/module');
    const { foo } = module;
    expect(foo()).toEqual('fooBAR');
  });

  it('do not mock bar 2', async() => {
    expect(foo()).toEqual('foobar');
  });
});

test / helpers.ts

export function mockModulePartially(
  modulePath: string,
  mocksCreator: (originalModule: any) => Record<string, any>
): void {
  const testRelativePath = path.relative(path.dirname(expect.getState().testPath), __dirname);
  const fixedModulePath = path.relative(testRelativePath, modulePath);
  jest.doMock(fixedModulePath, () => {
    const originalModule = jest.requireActual(fixedModulePath);
    return { ...originalModule, ...mocksCreator(originalModule) };
  });
}

모듈의 모의 기능은 mockModulePartially별도의 파일에있는 도우미 기능으로 이동되어 다른 테스트 파일 (일반적으로 다른 디렉터리에 위치 할 수 있음)에서 사용할 수 있습니다. 모의되는 expect.getState().testPath모듈 ( modulePath)에 대한 경로를 수정 하는 데 의존 합니다 ( helpers.ts포함에 상대적으로 지정 mockModulePartially). mocksCreator두 번째 인수로 전달 된 함수 mockModulePartially는 모듈의 모의를 반환해야합니다. 이 함수는 수신 originalModule하고 모의 구현은 선택적으로 의존 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.