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.