Ketergantungan mengolok-olok dengan naskah ketikan


94

Saat menguji modul yang memiliki ketergantungan pada file yang berbeda. Ketika menugaskan modul itu menjadi jest.Mockskrip ketikan memberikan kesalahan bahwa metode mockReturnThisOnce(atau metode jest.Mock lainnya) tidak ada pada ketergantungan, ini karena sebelumnya diketik. Apa cara yang tepat untuk mendapatkan ketikan untuk mewarisi tipe dari jest.Mock?

Berikut ini contoh singkatnya.

Ketergantungan

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

Saya merasa ini adalah kasus penggunaan yang sangat umum dan tidak yakin cara mengetiknya dengan benar. Bantuan apa pun akan sangat dihargai!


1
Jika saya ingat benar Anda harus mengejek sebelum Anda mengimpor. Ganti saja 2 baris pertama. Tapi saya tidak yakin tentang ini.
Thomas

3
@ ThomasKleßen Modul yang diimpor melalui ES6 importdievaluasi terlebih dahulu, tidak peduli jika Anda meletakkan beberapa kode sebelum mengimpor. Jadi ini tidak akan berhasil.
mgol

@Thomas Panggilan ke jest.mock diangkat ke atas kode - sihir lelucon saya kira ... ( ref ) Namun, ini menciptakan beberapa jebakan, misalnya saat memanggil jest.mock () dengan parameter pabrik modul oleh karena itu namai fungsi tiruan sebagaimock...
Tobi

Jawaban:


98

Anda dapat menggunakan tipe casting dan Anda test.tsakan terlihat seperti ini:

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

Transpiler TS tidak mengetahui adanya jest.mock('../dependency');perubahan tipe depsehingga Anda harus menggunakan tipe casting. Karena import depbukanlah definisi tipe, Anda harus mendapatkan tipenya typeof dep.default.

Berikut adalah beberapa pola berguna lainnya yang saya temukan selama bekerja dengan Jest dan TS

Ketika elemen yang diimpor adalah sebuah kelas maka Anda tidak harus menggunakan typeof misalnya:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

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

Solusi ini juga berguna ketika Anda harus memalsukan beberapa modul asli node:

import { existsSync } from 'fs';

jest.mock('fs');

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

Jika Anda tidak ingin menggunakan mock otomatis bercanda dan lebih suka membuat yang 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()membuat contoh objek tiruan TestedClassDependencydapat berupa kelas atau jenis atau antarmuka


3
Saya harus menggunakan jest.fn(() =>...alih-alih jest.fn<TestedClassDependency>(() =>...(saya baru saja menghapus jenis casting setelah jest.fn) karena IntelliJ mengeluh. Kalau tidak, jawaban ini membantu saya, terima kasih! Menggunakan ini di package.json saya: "@ types / jest": "^ 24.0.3"
A. Masson

apa jest.mock('./SomeClass');dalam kode di atas?
Reza

11
Bersenandung itu tidak berfungsi lagi dengan versi TS terakhir dan lelucon 24 :(
Vincent


6
The <jest.Mock<SomeClass>>SomeClassekspresi menghasilkan kesalahan TS bagi saya: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)
the21st

62

Gunakan mockedhelper dari ts-jestseperti yang dijelaskan di sini

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

dan jika

  • Kau gunakan tslint
  • ts-jest ada dalam dependensi dev Anda,

tambahkan aturan ini ke tslint.json:"no-implicit-dependencies": [true, "dev"]


Berikut adalah beberapa contoh penggunaan ts-jestdan kelas: github.com/tbinna/ts-jest-mock-examples dan pos ini: stackoverflow.com/questions/58639737/…
Tobi

5
Ini adalah jawaban yang jauh lebih baik daripada jawaban yang paling banyak dipilih.
fakeplasticandroid

@Tobi Tes dalam repo gagal
Kreator

Terima kasih atas perhatiannya kepada @Kreator. Apakah Anda melihat masalah yang sama dengan yang dilaporkan ? Saya belum bisa mereproduksi masalah apa pun.
Tobi

@Kreator baru saja menggabungkan PR. Beri tahu saya jika masalah terus berlanjut
Tobi

18

Saya menggunakan pola dari @ types / jest / index.d.ts tepat di atas tipe def untuk Mocked (baris 515):

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

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

2
Saya cukup yakin Anda bisa melakukannyaconst myApi = new Api() as jest.Mocked<Api>;
snowfrogdev

4
@neoflash: Tidak dalam mode ketat di TypeScript 3.4 - ini akan mengeluh bahwa jenis Api tidak cukup tumpang tindih dengan jest.Mock<Api>. Anda harus pergi dengan const myApi = new Api() as any as jest.Mock<Api>dan saya akan mengatakan yang di atas terlihat sedikit lebih baik daripada pernyataan ganda.
paolostyle

@tuptus: apakah mode ketat baru untuk 3.4? Apakah Anda memiliki tautan, tolong tentang ini?
elmpp

@elmpp: tidak yakin apa yang Anda maksud. Yang saya maksud dengan "mode ketat" adalah "strict": truedi tsconfig.json. Ini mencakup hal-hal seperti noImplicitAny, strictNullChecksdll., Jadi Anda tidak perlu menyetelnya ke true untuk mereka satu per satu.
paolostyle

Saya tidak mengerti. Mengapa Anda hanya menghentikan metode satu contoh, yaitu myApi? Itu tidak akan secara umum menghentikan semua instance lain yang diprakarsai oleh kelas Apidalam modul yang sedang diuji, bukan?
Ivan Wang

14

Ada dua solusi, keduanya adalah fungsi casting yang diinginkan

1) Gunakan jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

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

2) Gunakan jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

Tidak ada perbedaan antara kedua solusi ini. Yang kedua lebih pendek dan oleh karena itu saya menyarankan untuk menggunakan yang itu.

Kedua solusi casting memungkinkan untuk memanggil fungsi lelucon apa pun di mockMyFunctionsuka mockReturnValueatau mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction dapat digunakan secara normal untuk harapan

expect(mockMyFunction).toHaveBeenCalledTimes(1);

7

Pemeran as jest.Mock

Cukup casting fungsi untuk jest.Mockmelakukan trik:

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


6

Inilah yang saya lakukan dengan jest@24.8.0 dan ts-jest@24.0.2 :

sumber:

class OAuth {

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

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

uji:

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

Ini adalah cara memalsukan kelas non-default dan metode statisnya:

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

Berikut ini adalah beberapa jenis konversi dari jenis kelas Anda ke jest.MockedClassatau semacamnya. Tapi selalu berakhir dengan kesalahan. Jadi saya langsung menggunakannya, dan berhasil.

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

Tapi, jika itu sebuah fungsi, Anda bisa mengejeknya dan melakukan percakapan tipe.

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

Saya telah menemukan ini di @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);
*/

Catatan: Ketika Anda melakukannya const mockMyFunction = myFunctiondan kemudian sesuatu seperti mockFunction.mockReturnValue('foo'), Anda juga berubah myFunction.

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


1

Gunakan as jest.Mockdan tidak ada yang lain

Cara paling ringkas untuk mengejek modul yang diekspor seperti defaultdi ts-jest yang dapat saya anggap benar-benar bermuara pada casting modul sebagai jest.Mock.

Kode:

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

Manfaat:

  • tidak perlu merujuk ke defaultproperti di mana pun dalam kode pengujian - Anda merujuk nama fungsi yang diekspor sebenarnya,
  • Anda dapat menggunakan teknik yang sama untuk mengejek ekspor bernama,
  • tidak * asdalam pernyataan impor,
  • tidak ada transmisi yang rumit menggunakan typeofkata kunci,
  • tidak ada ketergantungan tambahan seperti mocked.

0

Pustaka terbaru memecahkan masalah ini dengan plugin babel: https://github.com/userlike/joke

Contoh:

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

Sadarilah itu depdan mockReturnValueOncesepenuhnya aman. Di atas, tsserver menyadari bahwa depencencyitu diimpor dan ditugaskan depsehingga semua pemfaktoran ulang otomatis yang didukung tsserver akan bekerja juga.

Catatan: Saya memelihara perpustakaan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.