Dépendance simulée en plaisantant avec dactylographié

94

Lors du test d'un module qui a une dépendance dans un fichier différent. Lorsque vous attribuez à ce module un script typographique, jest.Mockune erreur mockReturnThisOnceindique que la méthode (ou toute autre méthode jest.Mock) n'existe pas sur la dépendance, c'est parce qu'elle est précédemment tapée. Quelle est la bonne façon d'obtenir du typecript pour hériter des types de jest.Mock?

Voici un exemple rapide.

Dépendance

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

J'ai l'impression que c'est un cas d'utilisation très courant et je ne sais pas comment le saisir correctement. Toute aide serait très appréciée!

Philippe Chmalts
la source
1
Si je me souviens bien, vous devez vous moquer avant d'importer. Changez simplement les 2 premières lignes. Mais je n'en suis pas sûr.
Thomas
3
@ ThomasKleßen Les modules importés via ES6 importsont évalués en premier, peu importe si vous mettez du code avant l'importation. Donc ça ne marchera pas.
mgol
@Thomas Les appels à jest.mock sont hissés en haut du code - jest magic je suppose ... ( ref ) Cependant, cela crée des pièges, par exemple lors de l' appel de jest.mock () avec le paramètre module factory donc nommez les fonctions simulées asmock...
Tobi le

Réponses:

98

Vous pouvez utiliser la diffusion de type et votre test.tsdevrait ressembler à ceci:

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 n'est pas conscient du fait que les jest.mock('../dependency');changements de type depdoivent être utilisés. Comme importé depn'est pas une définition de type, vous devez obtenir son type avec typeof dep.default.

Voici quelques autres modèles utiles que j'ai trouvés lors de mon travail avec Jest et TS

Lorsque l'élément importé est une classe, vous n'avez pas besoin d'utiliser typeof par exemple:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

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

Cette solution est également utile lorsque vous devez vous moquer de certains modules natifs de nœuds:

import { existsSync } from 'fs';

jest.mock('fs');

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

Au cas où vous ne voudriez pas utiliser la simulation automatique de plaisanterie et préférez en créer une manuelle

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()crée une instance d'objet simulé TestedClassDependencypeut être une classe, un type ou une interface

Artur Górski
la source
3
J'ai dû utiliser à la jest.fn(() =>...place de jest.fn<TestedClassDependency>(() =>...(je viens de supprimer le type de casting après jest.fn) car IntelliJ se plaint. Sinon cette réponse m'a aidé merci! En utilisant ceci dans mon package.json: "@ types / jest": "^ 24.0.3"
A. Masson
que fait le jest.mock('./SomeClass');code ci-dessus?
Reza
11
Hum ça ne marche plus avec la dernière version TS et plaisanterie 24 :(
Vincent
1
@Reza c'est une simulation automatique, jestjs.io/docs/en/es6-class-mocks#automatic-mock
Bruce Lee
5
L' <jest.Mock<SomeClass>>SomeClassexpression produit une erreur TS pour moi: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
61

Utilisez l' mockedaide de ts-jestcomme expliqué ici

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

et si

  • tu utilises tslint
  • ts-jest est dans vos dépendances de développement,

ajoutez cette règle à votre tslint.json:"no-implicit-dependencies": [true, "dev"]

François Romain
la source
Voici quelques exemples supplémentaires d'utilisation ts-jestet de classes: github.com/tbinna/ts-jest-mock-examples et cet article: stackoverflow.com/questions/58639737/…
Tobi le
4
C'est une bien meilleure réponse que la réponse la plus votée.
fakeplasticandroid
@Tobi Le test du dépôt échoue
Kreator
Merci pour le heads-up @Kreator. Voyez-vous le même problème que celui signalé ? Je n'ai pas encore pu reproduire de problème.
Tobi
@Kreator vient de fusionner un PR. Faites-moi savoir si le problème persiste
Tobi le
18

J'utilise le modèle de @ types / jest / index.d.ts juste au-dessus du type def pour Mocked (ligne 515):

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

const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
adanilev
la source
2
Je suis presque sûr que vous pouvez le faireconst myApi = new Api() as jest.Mocked<Api>;
snowfrogdev
4
@neoflash: Pas en mode strict dans TypeScript 3.4 - il se plaindra que le type d'API ne se chevauche pas suffisamment avec jest.Mock<Api>. Vous devrez y aller const myApi = new Api() as any as jest.Mock<Api>et je dirais que celle ci-dessus est un peu meilleure que la double affirmation.
paolostyle
@tuptus: le mode strict est-il frais pour 3.4? Avez-vous un lien à ce sujet?
elmpp
@elmpp: je ne sais pas ce que vous voulez dire. Par "mode strict", je voulais dire avoir "strict": truedans tsconfig.json. Cela couvre des choses comme noImplicitAny, strictNullChecksetc., vous n'avez donc pas à le définir individuellement pour eux.
paolostyle
Je ne comprends pas. Pourquoi ne stubbing-tu que la méthode d'une seule instance, c'est myApi-à- dire ? Il ne stub de manière générique toutes les autres instances initiées par la classe Apidans le module testé, non?
Ivan Wang
14

Il existe deux solutions, les deux sont la fonction souhaitée

1) Utilisez jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

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

2) Utilisez jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

Il n'y a aucune différence entre ces deux solutions. Le second est plus court et je suggère donc de l'utiliser.

Les deux solutions de casting permettent d'appeler n'importe quelle fonction de simulation de plaisanterie sur mockMyFunctionlike mockReturnValueou mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction peut être utilisé normalement pour attendre

expect(mockMyFunction).toHaveBeenCalledTimes(1);
Noir
la source
7

Jeter as jest.Mock

Le simple fait de lancer la fonction en jest.Mockdevrait faire l'affaire:

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

exmaxx
la source
6

Voici ce que j'ai fait avec [email protected] et [email protected] :

la source:

class OAuth {

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

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

tester:

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

Voici comment se moquer d'une classe non par défaut et de ses méthodes statiques:

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

Voici une conversion de type du type de votre classe vers jest.MockedClassou quelque chose comme ça. Mais cela finit toujours par des erreurs. Je l'ai donc utilisé directement et cela a fonctionné.

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

Mais, si c'est une fonction, vous pouvez vous en moquer et faire la conversation de type.

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
la source
4

J'ai trouvé ceci dans @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);
*/

Remarque: Lorsque vous faites const mockMyFunction = myFunctionet puis quelque chose comme mockFunction.mockReturnValue('foo'), vous changez myFunctionégalement.

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

Milo
la source
0

Une bibliothèque récente résout ce problème avec un plugin babel: https://github.com/userlike/joke

Exemple:

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

Soyez conscient de cela depet mockReturnValueOncesont entièrement sûrs. De plus, tsserver sait qu'il a depencencyété importé et auquel il a été assigné afin depque toutes les refactorisations automatiques prises en charge par tsserver fonctionnent également.

Remarque: je gère la bibliothèque.

mostruash
la source