Simulacro de dependencia en broma con mecanografiado

94

Al probar un módulo que tiene una dependencia en un archivo diferente. Al asignar ese módulo para ser jest.Mockmecanografiado da un error de que el método mockReturnThisOnce(o cualquier otro método jest.Mock) no existe en la dependencia, esto se debe a que se tecleó previamente. ¿Cuál es la forma correcta de obtener mecanografiado para heredar los tipos de jest.Mock?

He aquí un ejemplo rápido.

Dependencia

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')
}

Siento que este es un caso de uso muy común y no estoy seguro de cómo escribirlo correctamente. ¡Cualquier ayuda será muy apreciada!

Philip Chmalts
fuente
1
Si no recuerdo mal, tienes que burlarte antes de importar. Simplemente cambie las primeras 2 líneas. Pero no estoy seguro de esto.
Thomas
3
@ ThomasKleßen Los módulos importados a través de ES6 importse evalúan primero, sin importar si coloca algún código antes de la importación. Entonces esto no funcionará.
mgol
@Thomas Las llamadas a jest.mock se elevan a la parte superior del código - jest magic, supongo ... ( ref ) Sin embargo, esto crea algunas trampas, por ejemplo, al llamar a jest.mock () con el parámetro de fábrica del módulo, por lo tanto, nombrar funciones simuladas comomock...
Tobi

Respuestas:

98

Puede utilizar la conversión de tipos y test.tsdebería verse así:

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');
});

TS transpiler no es consciente de que jest.mock('../dependency');cambia el tipo de, deppor lo tanto, debe usar el tipo de conversión. Como importado depno es una definición de tipo, debe obtener su tipo typeof dep.default.

Aquí hay algunos otros patrones útiles que encontré durante mi trabajo con Jest y TS

Cuando el elemento importado es una clase, no tiene que usar typeof, por ejemplo:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

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

Esta solución también es útil cuando tiene que simular algunos módulos nativos de nodo:

import { existsSync } from 'fs';

jest.mock('fs');

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

En caso de que no desee utilizar la simulación automática de broma y prefiera crear una 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()crea una instancia de objeto simulado TestedClassDependencypuede ser de clase, de tipo o de interfaz

Artur Górski
fuente
3
Tuve que usar en jest.fn(() =>...lugar de jest.fn<TestedClassDependency>(() =>...(acabo de eliminar el tipo de conversión después de jest.fn) porque IntelliJ se está quejando. De lo contrario, esta respuesta me ayudó, ¡gracias! Usando esto en mi package.json: "@ types / jest": "^ 24.0.3"
A. Masson
¿Qué hace jest.mock('./SomeClass');en el código anterior?
Reza
11
Hum, ya no funciona con la última versión de TS y broma 24 :(
Vincent
1
@Reza es auto simulacro, jestjs.io/docs/en/es6-class-mocks#automatic-mock
Bruce Lee
5
La <jest.Mock<SomeClass>>SomeClassexpresión me produce un error de TS: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
61

Utilice el mockedayudante de ts-jestcomo se explica aquí

// 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)
})

y si

  • tu usas tslint
  • ts-jest está en tus dependencias de desarrollo,

agregue esta regla a su tslint.json:"no-implicit-dependencies": [true, "dev"]

François Romain
fuente
Aquí hay algunos ejemplos más de uso ts-jesty clases: github.com/tbinna/ts-jest-mock-examples y esta publicación: stackoverflow.com/questions/58639737/…
Tobi
4
Esta es una respuesta mucho mejor que la más votada.
fakeplasticandroid
@Tobi La prueba en el repositorio falla
Kreator
Gracias por el aviso @Kreator. ¿Ve el mismo problema que el informado ? Todavía no pude reproducir ningún problema.
Tobi
@Kreator acaba de fusionar un PR. Avísame si el problema persiste
Tobi
18

Utilizo el patrón de @ types / jest / index.d.ts justo encima del type def para Mocked (línea 515):

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

const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
adanilev
fuente
2
Estoy bastante seguro de que podrías hacerloconst myApi = new Api() as jest.Mocked<Api>;
snowfrogdev
4
@neoflash: no en modo estricto en TypeScript 3.4; se quejará de que el tipo de Api no se superpone lo suficiente con jest.Mock<Api>. Tendrías que seguir const myApi = new Api() as any as jest.Mock<Api>y diría que el de arriba se ve un poco mejor que una doble afirmación.
paolostyle
@tuptus: ¿el modo estricto es nuevo para 3.4? ¿Tiene un enlace con respecto a esto?
elmpp
@elmpp: no estoy seguro de lo que quieres decir. Por "modo estricto" me refiero a tener "strict": trueen tsconfig.json. Esto cubre cosas como noImplicitAny, strictNullChecksetc., por lo que no tiene que configurarlo como verdadero para ellos individualmente.
paolostyle
No lo entiendo. ¿Por qué solo está eliminando el método de una instancia, es decir myApi? No eliminará genéricamente todas las demás instancias iniciadas por la clase Apidentro del módulo que se está probando, ¿verdad?
Ivan Wang
14

Hay dos soluciones, ambas tienen la función deseada

1) Usa broma.

import * as dep from './dependency';

jest.mock('./dependency');

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

2) Usa broma.

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

No hay diferencia entre estas dos soluciones. El segundo es más corto y, por lo tanto, sugeriría usar ese.

Ambas soluciones de fundición permiten llamar a cualquier función simulada de broma en me mockMyFunctiongusta mockReturnValueo mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction se puede utilizar normalmente para esperar

expect(mockMyFunction).toHaveBeenCalledTimes(1);
Negro
fuente
7

Emitir as jest.Mock

Simplemente lanzando la función a jest.Mockdebería hacer el truco:

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

exmaxx
fuente
6

Esto es lo que hice con [email protected] y [email protected] :

fuente:

class OAuth {

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

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

prueba:

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
  });
});

Así es como simular una clase no predeterminada y sus métodos estáticos:

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

Aquí debería haber alguna conversión de tipo del tipo de su clase a jest.MockedClasso algo así. Pero siempre termina con errores. Así que lo usé directamente y funcionó.

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

Pero, si es una función, puedes burlarte de ella y hacer el tipo de conversación.

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
>;
Bruce Lee
fuente
4

He encontrado esto en @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: Cuando lo haces const mockMyFunction = myFunctiony luego algo como mockFunction.mockReturnValue('foo'), también estás cambiando myFunction.

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

Milo
fuente
0

Una biblioteca reciente resuelve este problema con un complemento de babel: https://github.com/userlike/joke

Ejemplo:

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');
}

Tenga en cuenta que depy mockReturnValueOnceson totalmente seguros. Además, tsserver sabe que depencencyse importó y se le asignó, por deplo que todas las refactorizaciones automáticas que admite tsserver también funcionarán.

Nota: mantengo la biblioteca.

mostruash
fuente