- vừa được xem lúc

Tìm hiểu về Jest Mocks Test phía frontend

0 0 36

Người đăng: Nguyễn Tâm

Theo Viblo Asia

Giới thiệu

Chắc hẳn không ai phủ nhận rằng UnitTest là 1 phần quan trọng trong giai đoạn phát triển phần mềm, đảm bảo cho code được coverage tránh các bug không mong muốn. Và mock là một công đoạn không thể thiếu khi viết UnitTest. Mock là 1 kĩ thuật để thay thế các dependencies của chủ thể muốn test (subject) mà chúng ta có thể định nghĩa cách thức nó hoạt động hoặc kết quả trả về. Trong bài viết này, mình xin giới thiệu với các bạn về 1 thư viện thường được các frontend developer sử dụng để mock, đó chính là Jest.

Jest Mocks

Mock Function

Mục tiêu của mock là để thay thế những thứ chúng ta không thể kiểm soát bằng những thứ chúng ta có thể kiểm soát, do đó chúng ta sẽ nên biết những tính năng cần thiết để thực hiện công việc này. Các tính năng mà mock có thể thay thế:

  • Các API calls
  • Áp đặt các kết quả trả về
  • Thay đổi cách thức hoạt động của function

Cách đơn giản nhất để tạo 1 Mock Function instance là sử dụng jest.fn() Bằng cách này, chúng ta có thể dễ dàng test các function


test("returns undefined by default", () => { const mock = jest.fn(); let result = mock("foo"); expect(result).toBeUndefined(); expect(mock).toHaveBeenCalled(); expect(mock).toHaveBeenCalledTimes(1); expect(mock).toHaveBeenCalledWith("foo");
});

chúng ta có thể thay đổi giá trị trả về, cách hoạt động hoặc cách promise được resolved

test("mock implementation", () => { const mock = jest.fn(() => "bar"); expect(mock("foo")).toBe("bar"); expect(mock).toHaveBeenCalledWith("foo");
}); test("also mock implementation", () => { const mock = jest.fn().mockImplementation(() => "bar"); expect(mock("foo")).toBe("bar"); expect(mock).toHaveBeenCalledWith("foo");
}); test("mock implementation one time", () => { const mock = jest.fn().mockImplementationOnce(() => "bar"); expect(mock("foo")).toBe("bar"); expect(mock).toHaveBeenCalledWith("foo"); expect(mock("baz")).toBe(undefined); expect(mock).toHaveBeenCalledWith("baz");
}); test("mock return value", () => { const mock = jest.fn(); mock.mockReturnValue("bar"); expect(mock("foo")).toBe("bar"); expect(mock).toHaveBeenCalledWith("foo");
}); test("mock promise resolution", () => { const mock = jest.fn(); mock.mockResolvedValue("bar"); expect(mock("foo")).resolves.toBe("bar"); expect(mock).toHaveBeenCalledWith("foo");
});

Dependency Injection

Một trong những cách hay được sử dụng là truyền trực tiếp các argument vào function mà bạn đang test, việc này cho phép bạn test chủ thể dễ dàng, assert cách mà mock function được gọi với những argument nào.

const doAdd = (a, b, callback) => { callback(a + b);
}; test("calls callback with arguments added", () => { const mockCallback = jest.fn(); doAdd(1, 2, mockCallback); expect(mockCallback).toHaveBeenCalledWith(3);
});

Cách tiếp cận này rất rõ ràng, dễ hiểu, dễ test (còn được gọi là SOLID) nhưng yêu cầu code của bạn phải hỗ trọ dependency injection. Nhưng thực tế thì nhiều code trong nhiều dự án không hỗ trợ, do đó Jest hỗ trợ việc mock các module và function không cần thông qua DI.

Các cách mocks với Jest

Có 3 loại mock được Jest hỗ trợ:

  • jest.fn: Mock 1 function
  • jest.mock: Mock 1 module (trong 1 file)
  • jest.spyOn: Spy hoặc mock 1 function

Giả sử chúng ta có 1 project với cấu trúc như sau:

├ example/

| └── app.js

| └── app.test.js

| └── math.js

Với ví dụ này, chúng ta sẽ test app.js và có thể sẽ giả lập cách các function trong math.js hoạt động, hoặc spy cách nó hoạt động. (ví dụ khá đơn giản để chúng ta có thể dễ dàng hiểu, trong thực tế thì math.js có thể là các function tính toán phức tạp, đôi khi chứa đựng các external API, file IO, ..)

//math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => b - a;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => b / a;
//app.js
import * as math from './math.js'; export const doAdd = (a, b) => math.add(a, b);
export const doSubtract = (a, b) => math.subtract(a, b);
export const doMultiply = (a, b) => math.multiply(a, b);
export const doDivide = (a, b) => math.divide(a, b);

Mock 1 function với jest.fn

1 cách cơ bản để mock là assign lại 1 function bằng Mock Funciton. Vì vậy, bất cứ khi nào function được mock được gọi, thì mock function sẽ được sử dụng thay vì là function gốc ban đầu.

//mock_jest_fn.test.js
import * as app from "./app";
import * as math from "./math"; math.add = jest.fn();
math.subtract = jest.fn(); test("calls math.add", () => { app.doAdd(1, 2); expect(math.add).toHaveBeenCalledWith(1, 2);
}); test("calls math.subtract", () => { app.doSubtract(1, 2); expect(math.subtract).toHaveBeenCalledWith(1, 2);
});

Mock 1 module với jest.mock

1 cách tiếp cận được sử dụng nhiều hơn là dùng jest.mock để tự động mock tất cả export functions của 1 module. Khi gọi jest.mock('./math.js') tương đương với

export const add = jest.fn();
export const subtract = jest.fn();
export const multiply = jest.fn();
export const divide = jest.fn();

Từ đó, chúng ta có thể coi các exported function từ module là 1 mock function và mock theo cách mà ta muốn.

//mock_jest_mock.test.js
import * as app from "./app";
import * as math from "./math"; // Set all module functions to jest.fn
jest.mock("./math.js"); test("calls math.add", () => { app.doAdd(1, 2); expect(math.add).toHaveBeenCalledWith(1, 2);
}); test("calls math.subtract", () => { app.doSubtract(1, 2); expect(math.subtract).toHaveBeenCalledWith(1, 2);
});

Cách tiếp cận này được thực hiện tự động nếu set automock: true Điểm yếu của nó là chúng ta khó có thể dùng function gốc của module đó, trong trường hợp đó, ta có thể dùng spyOn.

Spy or mock 1 function với jest.spyOn

Đôi khi bạn chỉ muốn xem cách 1 method được gọi, nhưng vẫn giữ original implementation. Hoặc bạn muốn mock cách function được implementation nhưng sau đó phục hồi function gốc sau đó. jest.spyOn được sinh ra dành cho trường hợp này. Ở đây, chúng ta chỉ đơn giản "spy" cách math function được gọi, nhưng vẫn giữ nguyên cách nó được implement. Cách này rất hữu ích khi bạn muốn test các side-effect xảy ra khi function được gọi mà không thay thế cách nó thực sự hoạt động.

mock_jest_spyOn.test.js
import * as app from "./app";
import * as math from "./math"; test("calls math.add", () => { const addMock = jest.spyOn(math, "add"); // calls the original implementation expect(app.doAdd(1, 2)).toEqual(3); // and the spy stores the calls to add expect(addMock).toHaveBeenCalledWith(1, 2);
});

Trong trường hợp khác, bạn có thể muốn mock cách function được implement, nhưng sau đó phục hồi. Cách này hữu ích khi bạn muốn test trong cùng 1 file.

//mock_jest_spyOn_restore.test.js
import * as app from "./app";
import * as math from "./math"; test("calls math.add", () => { const addMock = jest.spyOn(math, "add"); // override the implementation addMock.mockImplementation(() => "mock"); expect(app.doAdd(1, 2)).toEqual("mock"); // restore the original implementation addMock.mockRestore(); expect(app.doAdd(1, 2)).toEqual(3);
});

Điểm lưu ý quan trọng ở đây là chúng lưu lại original function, sau đó mock implementation function đó, cuối cùng có thể phục hồi function gốc bằng biến đã lưu trước đó.

//mock_jest_spyOn_sugar.js 
import * as app from "./app";
import * as math from "./math"; test("calls math.add", () => { // store the original implementation const originalAdd = math.add; // mock add with the original implementation math.add = jest.fn(originalAdd); // spy the calls to add expect(app.doAdd(1, 2)).toEqual(3); expect(math.add).toHaveBeenCalledWith(1, 2); // override the implementation math.add.mockImplementation(() => "mock"); expect(app.doAdd(1, 2)).toEqual("mock"); expect(math.add).toHaveBeenCalledWith(1, 2); // restore the original implementation math.add = originalAdd; expect(app.doAdd(1, 2)).toEqual(3);
});

Kết bài

Như vậy, ở bài viết này chúng ta cơ bản đã hiểu được cách mà 1 mock function hoạt động và các cách tiếp cận mà Jest cung cấp để mock 1 function. Hi vọng bài viết hữu ích cho những bạn mới bắt đầu tìm hiểu về Jest testing. Cảm ơn các bạn đã đọc bài!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Thủ thuật nhỏ để căn chỉnh image với object-fit

Chào các bạn,. Có lẽ trong hành trình code của các bạn thì không ít lần gặp vấn đề méo ảnh do fix cứng cả width, height của ảnh nhỉ? Hoặc kể cả khi bạn set value cho 1 thuộc tính weigth hoặc height còn thuộc tính còn lại để auto thì nhiều lúc ảnh cũng không được hiển thị toàn vẹn cho lắm.

0 0 49

- vừa được xem lúc

Tìm hiểu về CSS framework - Bulma

Mở đầu:. Mấy bữa nay đang lướt web thấy có giới thiệu framework bulma này, được mọi người giới thiệu gọn nhẹ và dễ sử dụng, nên mình mới tìm hiểu thử và hôm nay xin viết 1 bài viết giới thiệu sơ qua với các bạn.

0 0 36

- vừa được xem lúc

Một số mẹo vặt "hay ho" của ES6 có thể bạn chưa biết - Phần 4

Xin chào, ở 3 bài trước của series "Một số mẹo vặt "hay ho" của ES6", mình đã chia sẻ 1 số tips/tricks nhỏ với ES6, hy vọng ít nhiều nó sẽ có ích với các bạn khi áp dụng vào thực tế. Hôm nay, xin mời các bạn theo dõi phần 4 của series này.

0 0 46

- vừa được xem lúc

Convert từ SVG sang Icon Font như thế nào?

Chào các bạn. Như câu hỏi trên title của bài viết, hôm nay mình sẽ hướng dẫn các bạn cách convert 1 file svg 1 cách khá đơn giản và vô cùng tiện lợi cho các bạn. https://icomoon.io/app/#/select.

0 0 53

- vừa được xem lúc

Một vài thủ thuật làm việc với các dạng layout - Phần 4

. Chào mọi người, cũng đã lâu rồi mình không thấy nhau. Để tiếp tục với series's về các dạng layout hôm nay mình sẽ chia sẻ thêm một trick thú vị nữa về step layout.

0 0 46

- vừa được xem lúc

[P2] VueJS - Một project Vue thì có cái gì ?

1. Cấu trúc project VueJS. Mình sẽ sử dụng lại project ở bài viết và đây là cấu trúc của project của chúng ta. .

0 0 83