Ao testar um módulo que possui uma dependência em um arquivo diferente. Quando atribuir esse módulo para ser jest.Mock
digitado 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!
javascript
unit-testing
typescript
jestjs
Philip Chmalts
fonte
fonte
import
são avaliados primeiro, não importa se você colocou algum código antes da importação. Então isso não vai funcionar.mock...
Respostas:
Você pode usar a conversão de tipo e
test.ts
deve 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,dep
portanto, você deve usar a conversão de tipo. Como importadodep
não é uma definição de tipo com a qual você deve obter seu tipotypeof 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 simuladoTestedClassDependency
pode ser classe, tipo ou interfacefonte
jest.fn(() =>...
vez dejest.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"jest.mock('./SomeClass');
no código acima?<jest.Mock<SomeClass>>SomeClass
expressã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)
Use o
mocked
ajudantets-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
tslint
ts-jest
está em suas dependências de desenvolvimento,adicione esta regra ao seu
tslint.json
:"no-implicit-dependencies": [true, "dev"]
fonte
ts-jest
e classes: github.com/tbinna/ts-jest-mock-examples e esta postagem: stackoverflow.com/questions/58639737/…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");
fonte
const myApi = new Api() as jest.Mocked<Api>;
jest.Mock<Api>
. Você teria que concordarconst 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."strict": true
em tsconfig.json. Isto cobre coisas comonoImplicitAny
,strictNullChecks
etc, para que você não tem que defini-la como verdade para eles individualmente.myApi
? Ele não genericamente stub todas as outras instâncias iniciadas pela classeApi
dentro do módulo sendo testado, certo?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
mockMyFunction
comomockReturnValue
oumockResolvedValue
https://jestjs.io/docs/en/mock-function-api.htmlmockMyFunction.mockReturnValue('value');
mockMyFunction
pode ser usado normalmente para esperarexpect(mockMyFunction).toHaveBeenCalledTimes(1);
fonte
Fundida
as jest.Mock
Basta lançar a função para
jest.Mock
deve resolver:(dep.default as jest.Mock).mockReturnValueOnce('return')
fonte
Aqui está o que fiz com [email protected] e [email protected] :
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.MockedClass
ou 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 >;
fonte
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 = myFunction
e então algo comomockFunction.mockReturnValue('foo')
, você está mudandomyFunction
.Fonte: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089
fonte
Use
as jest.Mock
e nada maisA maneira mais concisa de simular um módulo exportado como
default
em ts-jest que eu consigo pensar realmente se resume a lançar o módulo comojest.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:
default
propriedade em qualquer lugar no código de teste - em vez disso, você faz referência ao nome da função exportada real,* as
na declaração de importação,typeof
palavra chave,mocked
.fonte
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
dep
emockReturnValueOnce
são totalmente seguros para tipos. Além disso, tsserver está ciente de quedepencency
foi importado e foi atribuído a,dep
portanto, todas as refatorações automáticas que o tsserver suporta também funcionarão.Nota: Eu mantenho a biblioteca.
fonte
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.fonte