Rasgue Jest para entender o princípio

O teste unitário é inseparável do Jest, mas você sabe como o Jest é implementado? Especialmente o que diabos são jest.fn(), jest.mock(), jest.spyOn()?

Um: Primeiro veja o que é jest.fn()

imagem.png

As fotos mostram várias conclusões

  • jest é um objeto ou instância de classe, ele tem um método/função fn
  • Esta função fn aceita uma função
  • Após o processamento de fn, uma nova função é retornada, a saber [Function: mockConstructor]
  • A nova função retornou, inexplicavelmente adicionados novos métodos, como o getter de mock e mockImplementation
  • Portanto, este fn é uma função de fábrica que aceita uma função bruta e retorna uma função simulada

Então são três jogadores

  • Função de fábrica Função de fábrica
  • Função Original Função Original
  • Função simulada Função simulada

Dois: Continue a ver qual é o getter do mock retornado

imagem.png

  • Pode-se ver que este mock é uma coisa que armazena instâncias [esta instância] e resultados [resultado da execução], que não está listado por enquanto.

Três: A documentação do Jest mostra que as três chaves do objeto simples simulado são

  • Chamadas: Armazena os parâmetros de cada chamada para a função passiva
  • Instâncias: Este objeto que armazena cada chamada para a função original (cópia)
  • Resultados: Armazena o resultado de cada chamada para a função passiva

Existem apenas três resultados ao chamar uma função passiva

  • valor do resultado de retorno
  • não retornou nenhum valor, mas indefinido
  • erro de lançamento Erro

Quatro: O que diabos é esperar? Na verdade, é açúcar sintático exibir o objeto simulado.

  • expect(fn).toBeCalled()
  • expect(fn).toBeCalledTimes(n)
  • expect(fn).toBeCalledWith(arg1, arg2, ...)
  • expect(fn).lastCalledWith(arg1, arg2, ...)

Para facilitar a visualização dos resultados das três chaves do objeto simulado, o Jest fornece vários açúcares sintáticos acima. Por exemplo, .toBeCalledTime() é Results.length

5: Entenda o princípio do jest, vamos escrever uma função Jest de fábrica!

Primeiro simplesmente faça uma função de fábrica, ou seja, um conjunto. Nota: copie uma função original.

// object literal 
const jest={ }

function fn(impl= ()=>{} ) {

  const mockFn = function(...args) {
    return impl(...args); 
   };
  
  return mockFn;
}

jest.fn=fn;

复制代码

Interpretação: Este conjunto equivale a não fazer nada, apenas copiar a função original intacta.

teste:

imagem.png

Então vá além, use o closure closure para gravar o array de chamadas do mock

 

function fn(impl= ()=>{} ) {

  const mockFn = function(...args) {
    mockFn.mock.calls.push(args);
    return impl(...args); 
   };
   
  // 这里是mock对象对应的calls
  mockFn.mock = {
    calls: []
  };
  
  return mockFn;
}


复制代码

teste:

imagem.png

Interpretação:

  • 工厂函数利用闭包原理,包原始函数包裹,并且隔离起来,生成一个新的函数,同时附加了监控数组。
  • 注意⚠️:模拟函数跟原始函数不是一个函数。证明如下图。
  • 且,只有在调用模拟函数的时候才能被监控,调用原始函数没有意义,因为二者指向不同的内存地址,之所以调用模拟函数,是因为mock的意义就是这样。

imagem.png

最后完整实现


 function fn(impl = () => {}) {
  
   const mockFn = function(...args) {
     mockFn.mock.calls.push(args);
     mockFn.mock.instances.push(this);
     try {
      const value = impl.apply(this, args);  
        mockFn.mock.results.push({ type: 'return', value });
        return value;  
     } catch (value) {
       mockFn.mock.results.push({ type: 'throw', value });
       throw value;  
    }
   }
 
    mockFn.mock = { calls: [], instances: [], results: [] };
    return mockFn;
 }
复制代码

当然,真实的jest的mock函数比这个复杂,见 jestjs.io/docs/mock-f… 但是区别也仅是增加了更多的方法,如 mockImplementationOnce,等(见上图)

延伸总结

const mockedFunc= jest.fn( originalFunc );
mockedFunc可以看成是原始函数的加强版,注入了监控方法而已。其他核心没有变化。
即,此时如果调用mockedFunc(), 则相当于 originalFunc().

复杂的例子
const myMockFn = jest.fn(cb => cb(null, true));

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

step1: 简化 myMockFn ~= (cb)=> {
     return cb(null, true);
 }
 
step2: myMockFn((err, val) => console.log(val)) 相当于curry方法 
return console.log(true)



复制代码

继续理解除了监控方法外的其他方法

  • 除了监控数组mock之外(getter)
  • 还有其他方法被附加到了mockedFunc上面,比如mockImplementation,mockImplementationOnce,mockReturnValue等
  • 怎么理解?


function fn(impl = ()=>{} ) {

  const mockFn = function(...args) {
    mockFn.mock.calls.push(args);
    // 如果提供了value则直接短路,不执行原始函数
    if(mockReturnValue.value){
     return mockReturnValue.value;
    }
    
    // 如果提供替代的函数则跳过原始函数
    if(this.mockImplementation){
      return this.mockImplementation(...args);
    }
    return impl(...args); 
   };
   
  ...
  mockFn.mockReturnValue = (value)=>{
  this.mockReturnValue=value;
  };
  
  mockFn.mockImplementation = (func)=>{
  this.mockImplementation=func;
  };
  
  
  return mockFn;
}

复制代码

明白了上面的psuedo代码,看jest官方例子就简单了,比如

imagem.png

过程:

  1. 首先需要jest.mock('../foo') 相当于把jest.fn(foo)一下,即,套住foo;
  2. 如果没有1,则下面foo.mockImplementation...会报错,因为foo没有这个方法;
  3. 一旦使用了1,则原来的foo已经不再是原来的foo了; 而是一个副本,一个增加了方法的副本;
  4. foo.mockImplementation(() => 42); 其实就是

const newFoo= jest.fn(foo).mockImplementation(()=>42); 此时 newFoo()调用则绕过了原始的foo;当然由于3的存在,foo的名字没有变化,还是foo。

官方的这个例子是怎么回事?new哪里来的?

imagem.png

Depois de ler nossa caligrafia acima, devemos entender que a função de fábrica fn neste momento é na verdade um construtor de função, ou seja, nos tempos antigos, os programadores dependiam de construtores de função para substituir classes para gerar várias instâncias, ou seja, instance1= new myMock ()... Ao mesmo tempo, vincule isso e verifique:

imagem.png

mais exemplos

const { checkIfExists } = require("../../functions/simple");
const fs = require("fs");
const { readFileSync } = require("fs");

jest.mock("fs");
jest.mock("../../functions/simple");

// 尤其全局的mock;每次mock都会记录在mock的数组中;所以需要清空。
beforeEach(function () {
  jest.clearAllMocks();
});


describe("understand module mock w. fs", () => {

    test("should return true if file exists", () => {
        // 如果不mock;则mock module返回的是undefined;合理。需要你去mock implementation;
        // 如果不做,则类似 jest.fn();
        const fileName = "file.txt";
        const reading=  fs.readFileSync(fileName);

        console.log(reading);
        expect(fs.readFileSync.mock.calls[0][0]).toBe(fileName);

       
    });
});

describe("understand jest mock fun", () => {

 // 可以继续使用const不会跟mock的冲突
  test("method 1 to mock", () => {
    const checkIfExists1 = jest.fn(() => true);
    const value = checkIfExists1();
    expect(checkIfExists1).toBeCalledTimes(1);
    expect(checkIfExists1()).toBeTruthy();
    console.log(checkIfExists1.mock);
  });

 // toBeTruthy()必须调用();不然可以使用 mockednFunc.mock.results[0].value;
 // expect(checkIfExists).toBeTruthy(); 因为函数本身不是null,所以一定truthy
  test("method 2 to mock", () => {
    checkIfExists.mockImplementation(() => {
      console.log("mockImplementation");
    });

    checkIfExists();
    expect(checkIfExists).toBeCalledTimes(1);
    expect(checkIfExists).toBeTruthy();
  });

  test("method 3 to mock", () => {
    checkIfExists.mockImplementation();

    checkIfExists();
    expect(checkIfExists).toBeCalledTimes(1);
    expect(checkIfExists()).not.toBeTruthy();
  });

 // 如果mock的时候么有implementation等,则返回的是undefined; 合理,因为就是 jest.fn()
  test("method 4 to mock original", () => {
    checkIfExists();
    expect(checkIfExists).toBeCalledTimes(1);
    expect(checkIfExists()).toBeTruthy();
  });
  
});


describe("understand async mock", () => {
    // async 的mock和assert需要看官方文档
    test("method async 1 to mock", async () => {
        const mockedFetch = jest.fn(() => {
          return Promise.resolve({
            json: () =>
              Promise.resolve({
                data: 1,
              }),
          });
        });
    
        return expect(mockedFetch().then((res) => res.json())).resolves.toEqual({
          data: 1,
        });
      });
})
复制代码

Acho que você gosta

Origin juejin.im/post/7080493139764461605
Recomendado
Clasificación