때때로 일부 테스트에서는 함수를 모의하고 싶지만 다른 테스트에서는 모의하고 싶지 않은 경우가 있습니다. 때로는 서로 다른 테스트에 서로 다른 모의 객체를 제공하고 싶을 때가 있습니다. Jest는 이를 까다롭게 만듭니다. 기본 동작은 단일 테스트가 아닌 전체 테스트 파일에 대한 패키지 기능을 재정의하는 것입니다. Python의 @patch 또는 Laravel의 서비스 컨테이너와 같은 유연한 도구를 사용해 본 적이 있다면 이상하게 보일 것입니다.
이 게시물에서는 개별 테스트에 대해 함수를 모의하는 방법을 보여주고, 모의가 제공되지 않은 경우 원래 구현으로 대체합니다. CommonJS 및 ES 모듈 모두에 대한 예제가 제공됩니다. 이 게시물에서 설명하는 기술은 자사 모듈과 타사 패키지 모두에서 작동합니다.
이 게시물에서는 여러 모듈 시스템을 다룰 예정이므로 해당 시스템이 무엇인지 이해하는 것이 중요합니다.
CommonJS(약어로 CJS)는 Node.js의 모듈 시스템입니다. module.exports를 사용하여 함수를 내보내고 require():
를 사용하여 함수를 가져옵니다.
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet };
// CommonJS import const getUsersList = require('./greet');
ES 모듈(약칭 ESM)은 브라우저에서 사용하는 모듈 시스템입니다. 내보내기 키워드를 사용하여 함수를 내보내고 가져오기 키워드를 사용하여 함수를 가져옵니다.
// ES module export export default function greet() { return "Hello, world!"; }
// ES module import import { greet } from "./greet";
이 글을 쓰는 시점에는 대부분의 프런트엔드 JavaScript 개발자가 ES 모듈을 사용하고 있으며, 많은 서버 측 JS 개발자도 ES 모듈을 사용하고 있습니다. 그러나 CommonJS는 여전히 Node.js의 기본값입니다. 어떤 시스템을 사용하든 Jest의 조롱 시스템에 대해 알아보려면 전체 기사를 읽어볼 가치가 있습니다.
일반적으로 CommonJS 파일은 아래와 같이 객체 구문을 사용하여 모듈을 내보냅니다.
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet: greet };
그러나 함수 자체를 내보낼 수도 있습니다.
// CommonJS export function greet() { return "Hello, world!"; } module.exports = greet;
본인 코드에서 이 작업을 수행하는 것을 반드시 권장하지는 않습니다. 개체를 내보내면 애플리케이션을 개발하는 동안 골치 아픈 일이 줄어듭니다. 그러나 이는 CommonJS에서 내보낸 함수를 모의하는 방법을 논의한 다음 테스트에서 자체 구현을 제공하지 않는 경우 원본으로 대체하는 방법을 논의할 가치가 있을 만큼 충분히 일반적입니다.
테스트 중에 모의하려는 다음 CommonJS 파일이 있다고 가정해 보겠습니다.
// cjsFunction.js function testFunc() { return "original"; } module.exports = testFunc;
다음 코드를 사용하여 테스트에서 이를 모의할 수 있습니다.
const testFunc = require("./cjsFunction"); jest.mock("./cjsFunction"); beforeEach(() => { testFunc.mockImplementation(jest.requireActual("./cjsFunction")); }); it("can override the implementation for a single test", () => { testFunc.mockImplementation(() => "mock implementation"); expect(testFunc()).toBe("mock implementation"); expect(testFunc.mock.calls).toHaveLength(1); }); it("can override the return value for a single test", () => { testFunc.mockReturnValue("mock return value"); expect(testFunc()).toBe("mock return value"); expect(testFunc.mock.calls).toHaveLength(1); }); it("returns the original implementation when no overrides exist", () => { expect(testFunc()).toBe("original"); expect(testFunc.mock.calls).toHaveLength(1); });
jest.mock("./cjsFunction")을 호출하면 모듈(파일 및 모든 내보내기)이 자동 모의(문서)로 대체됩니다. 자동 모의가 호출되면 정의되지 않은 상태가 반환됩니다. 그러나 모의 구현, 반환 값 등을 재정의하는 메서드를 제공합니다. Jest Mock Functions 문서에서 제공되는 모든 속성과 메서드를 볼 수 있습니다.
모의 객체의 mockImplementation() 메서드를 사용하여 모의 객체의 구현을 원래 모듈의 구현으로 자동 설정할 수 있습니다. Jest는 현재 조롱되고 있는 경우에도 원본 모듈을 항상 로드하는 jest.requireActual() 메서드를 제공합니다.
모의 구현과 반환 값은 각 테스트 후에 자동으로 지워지므로 각 테스트 전에 모의 구현을 원래 구현으로 설정하는 Jest의 beforeEach() 함수에 콜백 함수를 전달할 수 있습니다. 그러면 자체 반환 값이나 구현을 제공하려는 모든 테스트는 테스트 본문 내에서 수동으로 이를 수행할 수 있습니다.
위의 코드가 단일 함수 대신 객체를 내보냈다고 가정해 보겠습니다.
// cjsModule.js function testFunc() { return "original"; } module.exports = { testFunc: testFunc, };
그러면 테스트는 다음과 같습니다.
const cjsModule = require("./cjsModule"); afterEach(() => { jest.restoreAllMocks(); }); it("can override the implementation for a single test", () => { jest .spyOn(cjsModule, "testFunc") .mockImplementation(() => "mock implementation"); expect(cjsModule.testFunc()).toBe("mock implementation"); expect(cjsModule.testFunc.mock.calls).toHaveLength(1); }); it("can override the return value for a single test", () => { jest.spyOn(cjsModule, "testFunc").mockReturnValue("mock return value"); expect(cjsModule.testFunc()).toBe("mock return value"); expect(cjsModule.testFunc.mock.calls).toHaveLength(1); }); it("returns the original implementation when no overrides exist", () => { expect(cjsModule.testFunc()).toBe("original"); }); it("can spy on calls while keeping the original implementation", () => { jest.spyOn(cjsModule, "testFunc"); expect(cjsModule.testFunc()).toBe("original"); expect(cjsModule.testFunc.mock.calls).toHaveLength(1); });
jest.spyOn() 메소드를 사용하면 Jest가 객체의 메소드에 대한 호출을 기록하고 자체 대체품을 제공할 수 있습니다. 이는 오직 객체에만 작동하며 모듈이 함수가 포함된 객체를 내보내므로 이를 사용할 수 있습니다.
spyOn() 메서드는 모의 메서드이므로 상태를 재설정해야 합니다. Jest spyOn() 문서에서는 위에서 수행한 afterEach() 콜백에서 jest.restoreAllMocks()를 사용하여 상태를 재설정할 것을 권장합니다. 이 작업을 수행하지 않으면 spyOn()이 호출된 후 다음 테스트에서 모의 개체가 정의되지 않은 상태를 반환하게 됩니다.
ES 모듈에는 기본 및 명명된 내보내기가 있을 수 있습니다.
// esmModule.js export default function () { return "original default"; } export function named() { return "original named"; }
위 파일에 대한 테스트는 다음과 같습니다.
import * as esmModule from "./esmModule"; afterEach(() => { jest.restoreAllMocks(); }); it("can override the implementation for a single test", () => { jest .spyOn(esmModule, "default") .mockImplementation(() => "mock implementation default"); jest .spyOn(esmModule, "named") .mockImplementation(() => "mock implementation named"); expect(esmModule.default()).toBe("mock implementation default"); expect(esmModule.named()).toBe("mock implementation named"); expect(esmModule.default.mock.calls).toHaveLength(1); expect(esmModule.named.mock.calls).toHaveLength(1); }); it("can override the return value for a single test", () => { jest.spyOn(esmModule, "default").mockReturnValue("mock return value default"); jest.spyOn(esmModule, "named").mockReturnValue("mock return value named"); expect(esmModule.default()).toBe("mock return value default"); expect(esmModule.named()).toBe("mock return value named"); expect(esmModule.default.mock.calls).toHaveLength(1); expect(esmModule.named.mock.calls).toHaveLength(1); }); it("returns the original implementation when no overrides exist", () => { expect(esmModule.default()).toBe("original default"); expect(esmModule.named()).toBe("original named"); });
이는 이전 CommonJS 예와 거의 같지만 몇 가지 주요 차이점이 있습니다.
먼저 모듈을 네임스페이스 가져오기로 가져옵니다.
import * as esmModule from "./esmModule";
그런 다음 기본 내보내기를 감시하려면 "default"를 사용합니다.
jest .spyOn(esmModule, "default") .mockImplementation(() => "mock implementation default");
Sometimes when trying to call jest.spyOn() with a third-party package, you'll get an error like the one below:
TypeError: Cannot redefine property: useNavigate at Function.defineProperty (<anonymous>)
When you run into this error, you'll need to mock the package that is causing the issue:
import * as reactRouterDOM from "react-router-dom"; // ADD THIS: jest.mock("react-router-dom", () => { const originalModule = jest.requireActual("react-router-dom"); return { __esModule: true, ...originalModule, }; }); afterEach(() => { jest.restoreAllMocks(); });
This code replaces the module with a Jest ES Module mock that contains all of the module's original properties using jest.mocks's factory parameter. The __esModule property is required whenever using a factory parameter in jest.mock to mock an ES module (docs).
If you wanted, you could also replace an individual function in the factory parameter. For example, React Router will throw an error if a consumer calls useNavigate() outside of a Router context, so we could use jest.mock() to replace that function throughout the whole test file if we desired:
jest.mock("react-router-dom", () => { const originalModule = jest.requireActual("react-router-dom"); return { __esModule: true, ...originalModule, // Dummy that does nothing. useNavigate() { return function navigate(_location) { return; }; }, }; });
I hope this information is valuable as you write your own tests. Not every app will benefit from being able to fallback to the default implementation when no implementation is provided in a test itself. Indeed, many apps will want to use the same mock for a whole testing file. However, the techniques shown in this post will give you fine-grained control over your mocking.
Let me know if I missed something or if there's something that I didn't include in this post that should be here.
위 내용은 Jest를 사용하여 개별 테스트에서 함수 재정의의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!