Mock Functions

https://jestjs.io/docs/mock-functions

Có hai cách để giả lập các hàm: Bằng cách tạo một hàm giả để sử dụng trong mã thử nghiệm hoặc viết một đoạn giả thủ công để ghi đè một phần phụ thuộc vào mô-đun.

Using a mock function

Hãy tưởng tượng chúng ta đang thử nghiệm việc triển khai một hàm forEach, hàm này gọi một hàm gọi lại cho mỗi mục trong một mảng được cung cấp.

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

Để kiểm tra hàm này, chúng ta có thể sử dụng một hàm giả và kiểm tra trạng thái của giả để đảm bảo gọi lại được gọi như mong đợi.

function sum(a, b) {
    return a + b;
}
module.exports = sum;

sum.test.js

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);

.mock property

Tất cả các hàm giả đều có thuộc tính .mock đặc biệt này, đây là nơi chứa dữ liệu về cách hàm đã được gọi và những gì hàm trả về được lưu giữ. Thuộc tính .mock cũng theo dõi giá trị của giá trị này cho mỗi cuộc gọi, vì vậy cũng có thể kiểm tra giá trị này:

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}
const myMock = jest.fn();
const a = new myMock();
const b = {};
const bound = myMock.bind(b);
bound();
console.log(myMock.mock.instances);

Các thành viên giả này rất hữu ích trong các bài kiểm tra để xác nhận cách các hàm này được gọi, khởi tạo hoặc những gì chúng trả về:

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// The function was called exactly once
expect(mockCallback.mock.calls.length).toBe(2);

// The first arg of the first call to the function was 'first arg'
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The second arg of the first call to the function was 'second arg'
expect(mockCallback.mock.calls[0][1]).toBe(undefined);

// The return value of the first call to the function was 'return value'
expect(mockCallback.mock.results[0].value).toBe(42);

// This function was instantiated exactly twice
expect(mockCallback.mock.instances.length).toBe(2);

Mock Return Values

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

Các hàm giả lập cũng rất hiệu quả trong mã sử dụng kiểu truyền tiếp nối hàm. Mã được viết theo phong cách này giúp tránh sự cần thiết của các đoạn khai phức tạp tạo lại hành vi của thành phần thực mà chúng đang sử dụng, có lợi cho việc đưa các giá trị trực tiếp vào thử nghiệm ngay trước khi chúng được sử dụng.

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
const filterTestFn = jest.fn();
// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12

Mocking Modules

// users.js
import axios from 'axios';
class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}
export default Users;

Bây giờ, để kiểm tra phương pháp này mà không thực sự nhấn vào API (và do đó tạo ra các bài kiểm tra chậm và dễ hỏng), chúng ta có thể sử dụng hàm jest.mock (...) để tự động mô phỏng mô-đun axios.

Khi chúng tôi mô phỏng mô-đun, chúng tôi có thể cung cấp một mockResolvedValue cho .get trả về dữ liệu mà chúng tôi muốn thử nghiệm của chúng tôi khẳng định chống lại. Trên thực tế, chúng tôi đang nói rằng chúng tôi muốn axios.get ('/ users.json') trả về một phản hồi giả.

// users.test.js
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);
  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))
  return Users.all().then(data => expect(data).toEqual(users));
});

Mock Implementations

Tuy nhiên, vẫn có những trường hợp hữu ích khi vượt ra ngoài khả năng chỉ định giá trị trả về và thay thế toàn bộ việc triển khai một hàm giả. Điều này có thể được thực hiện với jest.fn hoặc phương thức mockImplementationOnce trên các hàm giả.

C:\xampp\htdocs\php\sum.js

function sum(a, b) {
    return a + b;
}
module.exports = sum;

C:\xampp\htdocs\php\users.test.js

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true

Phương thức mockImplementation hữu ích khi bạn cần xác định triển khai mặc định của một hàm giả lập được tạo từ một mô-đun khác:

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
// test.js
jest.mock('./foo'); // this happens automatically with automocking
const foo = require('./foo');
// foo is a mock function
foo.mockImplementation(() => 42);
console.log(foo());
// > 42

Khi bạn cần tạo lại một hành vi phức tạp của một hàm giả để nhiều lệnh gọi hàm tạo ra các kết quả khác nhau, hãy sử dụng phương thức mockImplementationOnce:

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));
  myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

Khi hàm mocked chạy hết các triển khai được xác định bằng mockImplementationOnce, nó sẽ thực thi bộ cài đặt triển khai mặc định với jest.fn (nếu nó được định nghĩa):

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

Đối với những trường hợp chúng tôi có các phương thức thường được xâu chuỗi (và do đó luôn cần trả về giá trị này), chúng tôi có một API đường để đơn giản hóa điều này dưới dạng một hàm .mockReturnThis () cũng nằm trên tất cả các mô hình:

const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
const myObj = {
  myMethod: jest.fn().mockReturnThis(),
};
// is the same as
const otherObj = {
  myMethod: jest.fn(function () {
    return this;
  }),
};

Mock Names

Bạn có thể tùy chọn cung cấp tên cho các hàm giả của mình, tên này sẽ được hiển thị thay vì "jest.fn ()" trong đầu ra lỗi kiểm tra. Sử dụng điều này nếu bạn muốn có thể nhanh chóng xác định chức năng giả lập báo cáo lỗi trong đầu ra thử nghiệm của bạn.

const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockImplementation(scalar => 42 + scalar)
  .mockName('add42');

Custom Matchers

Cuối cùng, để giảm bớt yêu cầu xác nhận cách các hàm giả đã được gọi, chúng tôi đã thêm một số hàm đối sánh tùy chỉnh cho bạn:

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();
// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);
// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

These matchers are sugar for common forms of inspecting the .mock property. You can always do this manually yourself if that's more to your taste or if you need to do something more specific:// The mock function was called at least onceexpect(mockFunc.mock.calls.length).toBeGreaterThan(0);// The mock function was called at least once with the specified argsexpect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);// The last call to the mock function was called with the specified argsexpect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([ arg1, arg2,]);// The first arg of the last call to the mock function was `42`// (note that there is no sugar helper for this specific of an assertion)expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);// A snapshot will check that a mock was invoked the same number of times,// in the same order, with the same arguments. It will also assert on the name.expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);expect(mockFunc.getMockName()).toBe('a mock name');Copy

For a complete list of matchers, check out the reference docs.

Last updated