Dependência simulada em brincadeira com o texto


100

Ao testar um módulo que possui uma dependência em um arquivo diferente. Quando atribuir esse módulo para ser jest.Mockdigitado dá um erro de que o método mockReturnThisOnce(ou qualquer outro método jest.Mock) não existe na dependência, isso é porque ele foi digitado anteriormente. Qual é a maneira correta de obter o texto digitado para herdar os tipos de jest.Mock?

Aqui está um exemplo rápido.

Dependência

const myDep = (name: string) => name;
export default myDep;

test.ts

import * as dep from '../depenendency';
jest.mock('../dependency');

it('should do what I need', () => {
  //this throws ts error
  // Property mockReturnValueOnce does not exist on type (name: string)....
  dep.default.mockReturnValueOnce('return')
}

Acho que este é um caso de uso muito comum e não tenho certeza de como digitá-lo corretamente. Qualquer ajuda seria muito apreciada!


2
Se bem me lembro, você tem que zombar antes de importar. Basta trocar as 2 primeiras linhas. Mas não tenho certeza sobre isso.
— Thomas

3
@ ThomasKleßen Módulos importados via ES6 importsão avaliados primeiro, não importa se você colocou algum código antes da importação. Então isso não vai funcionar.
— mgol

@Thomas Chamadas para jest.mock são içadas para o topo do código - mágica de jest, eu acho ... ( ref ) No entanto, isso cria algumas armadilhas, por exemplo, ao chamar jest.mock () com o parâmetro de fábrica do módulo, portanto, nomeie funções mock comomock...
— Tobi,

Respostas:


105

Você pode usar a conversão de tipo e test.tsdeve ser assim:

import * as dep from '../dependency';
jest.mock('../dependency');

const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;

it('should do what I need', () => {
  //this throws ts error
  // Property mockReturnValueOnce does not exist on type (name: string)....
  mockedDependency.mockReturnValueOnce('return');
});

O transpiler TS não está ciente das jest.mock('../dependency');alterações de tipo, depportanto, você deve usar a conversão de tipo. Como importado depnão é uma definição de tipo com a qual você deve obter seu tipo typeof dep.default.

Aqui estão alguns outros padrões úteis que encontrei durante meu trabalho com Jest e TS

Quando o elemento importado é uma classe, você não precisa usar typeof, por exemplo:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

const mockedClass = <jest.Mock<SomeClass>>SomeClass;

Esta solução também é útil quando você precisa simular alguns módulos nativos do nó:

import { existsSync } from 'fs';

jest.mock('fs');

const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;

Caso você não queira usar a simulação automática e preferir criar uma manual

import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';

const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
  // implementation
}));

it('Should throw an error when calling playSomethingCool', () => {
  const testedClass = new TestedClass(testedClassDependencyMock());
});

testedClassDependencyMock()cria instância de objeto simulado TestedClassDependencypode ser classe, tipo ou interface


3
Tive que usar em jest.fn(() =>...vez de jest.fn<TestedClassDependency>(() =>...(acabei de remover o tipo de fundição após jest.fn) porque o IntelliJ está reclamando. Caso contrário, esta resposta me ajudou, obrigado! Usando isso em meu package.json: "@ types / jest": "^ 24.0.3"
— A. Masson

o que faz jest.mock('./SomeClass');no código acima?
— Reza

12
Hum, ele não funciona mais com a última versão do TS e piada 24 :(
— Vincent


8
A <jest.Mock<SomeClass>>SomeClassexpressão está produzindo um erro de TS para mim:Conversion of type 'typeof SomeClass' to type 'Mock<SomeClass, any>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'typeof SomeClass' is missing the following properties from type 'Mock<SomeClass, any>': getMockName, mock, mockClear, mockReset, and 11 more.ts(2352)
— 21 de

64

Use o mockedajudantets-jest explicado aqui

// foo.spec.ts
import { mocked } from 'ts-jest/utils'
import { foo } from './foo'
jest.mock('./foo')

// here the whole foo var is mocked deeply
const mockedFoo = mocked(foo, true)

test('deep', () => {
  // there will be no TS error here, and you'll have completion in modern IDEs
  mockedFoo.a.b.c.hello('me')
  // same here
  expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})

test('direct', () => {
  foo.name()
  // here only foo.name is mocked (or its methods if it's an object)
  expect(mocked(foo.name).mock.calls).toHaveLength(1)
})

e se

  • você usa tslint
  • ts-jest está em suas dependências de desenvolvimento,

adicione esta regra ao seu tslint.json:"no-implicit-dependencies": [true, "dev"]


Aqui estão mais alguns exemplos de uso ts-jeste classes: github.com/tbinna/ts-jest-mock-examples e esta postagem: stackoverflow.com/questions/58639737/…
— Tobi

5
Esta é uma resposta muito melhor do que a mais votada.
— fakeplasticandroid

@Tobi O teste no repo falha
— Kreator

1
@ François Romain - o link neste post está morto
— jarodsmk

1
@jarodsmk Eu atualizei o link. obrigado
— François Romain

18

Eu uso o padrão de @ types / jest / index.d.ts logo acima do tipo def para Mocked (linha 515):

import { Api } from "../api";
jest.mock("../api");

const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");

2
Tenho certeza de que você poderia fazerconst myApi = new Api() as jest.Mocked<Api>;
— snowfrogdev

4
@neoflash: Não no modo estrito no TypeScript 3.4 - reclamará que o tipo de Api não se sobrepõe suficientemente ao jest.Mock<Api>. Você teria que concordar const myApi = new Api() as any as jest.Mock<Api>e eu diria que o acima parece um pouco melhor do que uma afirmação dupla.
— paolostyle

@tuptus: o modo estrito é novo para o 3.4? Você tem um link sobre isso?
— elmpp

@elmpp: não tenho certeza do que você quer dizer. Por "modo estrito" quis dizer ter "strict": trueem tsconfig.json. Isto cobre coisas como noImplicitAny, strictNullChecksetc, para que você não tem que defini-la como verdade para eles individualmente.
— paolostyle

Eu não entendo. Por que você está apenas copiando o método de uma instância, ou seja myApi? Ele não genericamente stub todas as outras instâncias iniciadas pela classe Apidentro do módulo sendo testado, certo?
— Ivan Wang

17

Existem duas soluções testadas para TypeScript versão 3.xe 4.x , ambas lançando a função desejada

1) Use jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;

2) Use jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

Não há diferença entre essas duas soluções. O segundo é mais curto e, portanto, sugiro usá-lo.

Ambas as soluções de fundição permitem chamar qualquer função mock de jest em mockMyFunctioncomo mockReturnValueou mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction pode ser usado normalmente para esperar

expect(mockMyFunction).toHaveBeenCalledTimes(1);

Obrigado! Gosto mais disso do que da resposta aceita porque a) é mais fácil de ler IMO eb) funciona em JSX sem causar erros de sintaxe
— Dan Fletcher

7

Fundida as jest.Mock

Basta lançar a função para jest.Mockdeve resolver:

(dep.default as jest.Mock).mockReturnValueOnce('return')


6

Aqui está o que fiz com jest@24.8.0 e ts-jest@24.0.2 :

fonte:

class OAuth {

  static isLogIn() {
    // return true/false;
  }

  static getOAuthService() {
    // ...
  }
}

teste:

import { OAuth } from '../src/to/the/OAuth'

jest.mock('../src/utils/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

describe('createMeeting', () => {
  test('should call conferenceLoginBuild when not login', () => {
    OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
      return false;
    });

    // Other tests
  });
});

Veja como simular uma classe não padrão e seus métodos estáticos:

jest.mock('../src/to/the/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

Aqui deve haver alguma conversão de tipo do tipo de sua classe para jest.MockedClassou algo parecido. Mas sempre termina com erros. Então, usei diretamente e funcionou.

test('Some test', () => {
  OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
    return false;
  });
});

Mas, se for uma função, você pode simular e fazer o tipo de conversa.

jest.mock('../src/to/the/Conference', () => ({
  conferenceSuccessDataBuild: jest.fn(),
  conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as 
jest.MockedFunction<
  typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as 
jest.MockedFunction<
  typeof conferenceSuccessDataBuild
>;

4

Eu encontrei isso em @types/jest:

/**
  * Wrap a function with mock definitions
  *
  * @example
  *
  *  import { myFunction } from "./library";
  *  jest.mock("./library");
  *
  *  const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
  *  expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/

Nota: Quando você faz const mockMyFunction = myFunctione então algo como mockFunction.mockReturnValue('foo'), você está mudandomyFunction .

Fonte: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089


4

Use as jest.Mocke nada mais

A maneira mais concisa de simular um módulo exportado como defaultem ts-jest que eu consigo pensar realmente se resume a lançar o módulo como jest.Mock.

Código:

import myDep from '../dependency' // No `* as` here

jest.mock('../dependency')

it('does what I need', () => {
  // Only diff with pure JavaScript is the presence of `as jest.Mock`
  (myDep as jest.Mock).mockReturnValueOnce('return')

  // Call function that calls the mocked module here

  // Notice there's no reference to `.default` below
  expect(myDep).toHaveBeenCalled()
})

Benefícios:

  • não requer referência à defaultpropriedade em qualquer lugar no código de teste - em vez disso, você faz referência ao nome da função exportada real,
  • você pode usar a mesma técnica para simular exportações nomeadas,
  • não * asna declaração de importação,
  • nenhum elenco complexo usando o typeof palavra chave,
  • sem dependências extras como mocked.

Eu te amo, finalmente após 4 horas minha busca chega ao fim.
— LoXatoR

0

Uma biblioteca recente resolve esse problema com um plug-in babel: https://github.com/userlike/joke

Exemplo:

import { mock, mockSome } from 'userlike/joke';

const dep = mock(import('./dependency'));

// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({ 
  thisIsAMock: jest.fn() 
}));

it('should do what I need', () => {
  dep.mockReturnValueOnce('return');
}

Esteja ciente de que depe mockReturnValueOncesão totalmente seguros para tipos. Além disso, tsserver está ciente de que depencencyfoi importado e foi atribuído a, depportanto, todas as refatorações automáticas que o tsserver suporta também funcionarão.

Nota: Eu mantenho a biblioteca.


0

Isso é feio e, na verdade, fugir dessa feiúra é o motivo de eu até mesmo examinar esta questão, mas para obter uma digitação forte a partir de uma simulação de módulo, você pode fazer algo assim:

const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;

jest.mock('./dependency');

Certifique-se de exigir, em './dependency'vez de simular diretamente, ou obterá duas instanciações diferentes.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.