Scheinabhängigkeit im Scherz mit Typoskript


94

Beim Testen eines Moduls, das eine Abhängigkeit in einer anderen Datei hat. Wenn Sie dieses Modul als jest.MockTyposkript zuweisen, wird der Fehler mockReturnThisOnceangezeigt, dass die Methode (oder eine andere jest.Mock-Methode) in der Abhängigkeit nicht vorhanden ist. Dies liegt daran, dass sie zuvor eingegeben wurde. Was ist der richtige Weg, um Typoskript dazu zu bringen, die Typen von jest.Mock zu erben?

Hier ist ein kurzes Beispiel.

Abhängigkeit

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

Ich bin der Meinung, dass dies ein sehr häufiger Anwendungsfall ist und ich nicht sicher bin, wie ich das richtig eingeben soll. Jede Hilfe wäre sehr dankbar!


1
Wenn ich mich richtig erinnere, musst du dich verspotten, bevor du importierst. Schalten Sie einfach die ersten 2 Zeilen. Aber da bin ich mir nicht sicher.
Thomas

3
@ ThomasKleßen Module, die über ES6 importiert wurden, importwerden zuerst ausgewertet, unabhängig davon, ob Sie vor dem Import Code eingeben. Das wird also nicht funktionieren.
mgol

@Thomas Aufrufe von jest.mock werden an den Anfang des Codes gehievt - Scherzmagie, denke ich ... ( ref ) Dies führt jedoch zu einigen Fallstricken, z. B. beim Aufrufen von jest.mock () mit dem Parameter factory factory, und benennen Sie daher Scheinfunktionen asmock...
Tobi

Antworten:


98

Sie können Type Casting verwenden und Ihr test.tssollte so aussehen:

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 ist sich nicht bewusst, dass jest.mock('../dependency');sich der Typ ändert, depdaher müssen Sie den Typ Casting verwenden. Da importiert depkeine Typdefinition ist, müssen Sie den Typ mit abrufen typeof dep.default.

Hier sind einige andere nützliche Muster, die ich während meiner Arbeit mit Jest und TS gefunden habe

Wenn das importierte Element eine Klasse ist, müssen Sie beispielsweise typeof nicht verwenden:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

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

Diese Lösung ist auch nützlich, wenn Sie einige native Knotenmodule verspotten müssen:

import { existsSync } from 'fs';

jest.mock('fs');

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

Für den Fall, dass Sie keinen automatischen Scherz-Mock verwenden möchten und lieber einen manuellen erstellen möchten

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()Erstellt eine verspottete Objektinstanz TestedClassDependencykann entweder eine Klasse oder ein Typ oder eine Schnittstelle sein


3
Ich musste jest.fn(() =>...stattdessen verwenden jest.fn<TestedClassDependency>(() =>...(ich habe gerade das Typ-Casting nach jest.fn entfernt), weil IntelliJ sich beschwert. Ansonsten hat mir diese Antwort geholfen, danke! Verwenden Sie dies in meinem package.json: "@ types / jest": "^ 24.0.3"
A. Masson

Was bedeutet der jest.mock('./SomeClass');obige Code?
Reza

11
Hum es funktioniert nicht mehr mit der letzten TS-Version und Scherz 24 :(
Vincent


6
Der <jest.Mock<SomeClass>>SomeClassAusdruck erzeugt einen TS-Fehler für mich: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.

62

Verwenden Sie den mockedHelfer ts-jestwie hier beschrieben

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

und wenn

  • Sie verwenden tslint
  • ts-jest liegt in deinen dev-Abhängigkeiten,

Fügen Sie diese Regel zu Ihrem hinzu tslint.json:"no-implicit-dependencies": [true, "dev"]


Hier sind einige weitere Beispiele für die Verwendung ts-jestund Klassen: github.com/tbinna/ts-jest-mock-examples und dieser Beitrag: stackoverflow.com/questions/58639737/…
Tobi

5
Dies ist eine viel bessere Antwort als die am höchsten gewählte.
fakeplasticandroid

@Tobi Der Test im Repo schlägt fehl
Kreator

Danke für das Heads-up @Kreator. Sehen Sie das gleiche Problem wie das gemeldete ? Ich konnte noch kein Problem reproduzieren.
Tobi

@Kreator hat gerade eine PR zusammengeführt. Lassen Sie mich wissen, ob das Problem weiterhin besteht
Tobi

18

Ich verwende das Muster aus @ types / jest / index.d.ts direkt über dem Typ def für Mocked (Zeile 515):

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

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

2
Ich bin mir ziemlich sicher, dass Sie es einfach tun könntenconst myApi = new Api() as jest.Mocked<Api>;
snowfrogdev

4
@neoflash: Nicht im strengen Modus in TypeScript 3.4 - es wird sich beschweren, dass sich der Api-Typ nicht ausreichend mit überschneidet jest.Mock<Api>. Sie müssten mitgehen const myApi = new Api() as any as jest.Mock<Api>und ich würde sagen, das obige sieht ein bisschen besser aus als die doppelte Behauptung.
Paolostyle

@tuptus: Ist der strikte Modus für 3.4 frisch? Haben Sie bitte einen Link dazu?
Elmpp

@elmpp: nicht sicher was du meinst. Mit "strenger Modus" meinte ich "strict": truein tsconfig.json. Zu dieser Kategorie gehören solche Sachen noImplicitAny, strictNullChecksetc., so dass Sie es wahr für sie individuell nicht festlegen müssen.
Paolostyle

Ich verstehe es nicht Warum stubben Sie nur die Methode einer Instanz, dh myApi? Es werden nicht generisch alle anderen Instanzen gestoppt, die von der Klasse Apiinnerhalb des zu testenden Moduls initiiert wurden , oder?
Ivan Wang

14

Es gibt zwei Lösungen, beide gießen die gewünschte Funktion

1) Verwenden Sie jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

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

2) Verwenden Sie jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

Es gibt keinen Unterschied zwischen diesen beiden Lösungen. Der zweite ist kürzer und ich würde daher vorschlagen, diesen zu verwenden.

Beide Casting-Lösungen ermöglichen das Aufrufen einer beliebigen Scherz-Mock-Funktion unter mockMyFunctionlike mockReturnValueoder mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction kann normalerweise für erwarten verwendet werden

expect(mockMyFunction).toHaveBeenCalledTimes(1);

7

Besetzung as jest.Mock

Einfach die Funktion zu übertragen, jest.Mocksollte den Trick tun:

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


6

Folgendes habe ich mit jest@24.8.0 und ts-jest@24.0.2 gemacht :

Quelle:

class OAuth {

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

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

Prüfung:

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

So verspotten Sie eine nicht standardmäßige Klasse und ihre statischen Methoden:

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

Hier sollte eine Typkonvertierung vom Typ Ihrer Klasse zu jest.MockedClassoder so ähnlich sein. Aber es kommt immer zu Fehlern. Also habe ich es einfach direkt benutzt und es hat funktioniert.

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

Wenn es sich jedoch um eine Funktion handelt, können Sie sie verspotten und die Typkonversation durchführen.

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

Ich habe dies gefunden in @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);
*/

Hinweis: Wenn Sie dies tun const mockMyFunction = myFunctionund dann so etwas mockFunction.mockReturnValue('foo'), ändern Sie sich myFunctionebenfalls.

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


1

Verwenden Sie as jest.Mockund sonst nichts

Die prägnanteste Art, ein wie defaultin ts-jest exportiertes Modul zu verspotten , die ich mir vorstellen kann, besteht darin, das Modul als zu gießen jest.Mock.

Code:

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

Leistungen:

  • erfordert keinen Verweis auf die defaultEigenschaft irgendwo im Testcode - Sie verweisen stattdessen auf den tatsächlich exportierten Funktionsnamen.
  • Sie können dieselbe Technik verwenden, um benannte Exporte zu verspotten.
  • nein * asin der import Anweisung,
  • kein komplexes Casting mit dem typeofSchlüsselwort,
  • Keine zusätzlichen Abhängigkeiten wie mocked.

0

Eine aktuelle Bibliothek löst dieses Problem mit einem Babel-Plugin: https://github.com/userlike/joke

Beispiel:

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

Beachten Sie dies depund mockReturnValueOncesind vollständig typsicher. Darüber hinaus ist tsserver bekannt, dass depencencyes importiert und zugewiesen wurdedep sodass alle von tsserver unterstützten automatischen Refactorings auch funktionieren.

Hinweis: Ich pflege die Bibliothek.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.