จำลองการพึ่งพาในเรื่องตลกด้วย typescript


94

เมื่อทดสอบโมดูลที่มีการอ้างอิงในไฟล์อื่น เมื่อกำหนดโมดูลนั้นให้เป็นjest.Mocktypescript จะทำให้เกิดข้อผิดพลาดที่เมธอดmockReturnThisOnce(หรือเมธอด jest.Mock อื่น ๆ ) ไม่มีอยู่ในการอ้างอิงนั่นเป็นเพราะมันถูกพิมพ์ไว้ก่อนหน้านี้ วิธีที่เหมาะสมในการรับ typescript เพื่อสืบทอดประเภทจาก jest.Mock คืออะไร?

นี่คือตัวอย่างสั้น ๆ

การพึ่งพา

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

ฉันรู้สึกว่านี่เป็นกรณีการใช้งานที่พบบ่อยมากและไม่แน่ใจว่าจะพิมพ์สิ่งนี้ได้อย่างไร ความช่วยเหลือใด ๆ จะได้รับการชื่นชมมาก!


2
ถ้าจำไม่ผิดคุณต้องล้อเลียนก่อนนำเข้า เพียงแค่สลับ 2 บรรทัดแรก แต่ฉันไม่แน่ใจเกี่ยวกับเรื่องนี้
โธมัส

3
โมดูลThomasKleßenที่นำเข้าผ่าน ES6 importจะได้รับการประเมินก่อนไม่ว่าคุณจะใส่โค้ดก่อนการนำเข้าก็ตาม สิ่งนี้จะไม่ได้ผล
mgol

@ โทมัสโทรไปที่ jest.mock ถูกยกขึ้นไปที่ด้านบนของรหัส - มายากลตลกฉันเดา ... ( อ้างอิง ) อย่างไรก็ตามสิ่งนี้สร้างข้อผิดพลาดบางอย่างเช่นเมื่อเรียก jest.mock () ด้วยพารามิเตอร์โรงงานโมดูลดังนั้นจึงตั้งชื่อฟังก์ชันจำลอง ณmock...
Tobi

คำตอบ:


98

คุณสามารถใช้การหล่อแบบและของคุณtest.tsควรมีลักษณะดังนี้:

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 ไม่ทราบว่าjest.mock('../dependency');การเปลี่ยนแปลงประเภทdepดังนั้นคุณต้องใช้การหล่อแบบ ขณะที่นำเข้าไม่ได้เป็นความหมายที่คุณพิมพ์จะต้องได้รับชนิดที่มีdeptypeof dep.default

นี่คือรูปแบบที่มีประโยชน์อื่น ๆ ที่ฉันพบระหว่างทำงานกับ Jest และ TS

เมื่ออิลิเมนต์ที่นำเข้าเป็นคลาสคุณไม่จำเป็นต้องใช้ typeof เช่น:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

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

โซลูชันนี้ยังมีประโยชน์เมื่อคุณต้องจำลองโมดูลเนทีฟของโหนด:

import { existsSync } from 'fs';

jest.mock('fs');

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

ในกรณีที่คุณไม่ต้องการใช้การเยาะเย้ยอัตโนมัติและต้องการสร้างด้วยตนเอง

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()สร้างอินสแตนซ์วัตถุจำลอง TestedClassDependencyสามารถเป็นคลาสหรือประเภทหรืออินเทอร์เฟซ


3
ฉันต้องใช้jest.fn(() =>...แทนjest.fn<TestedClassDependency>(() =>...(ฉันเพิ่งลบประเภทแคสติ้งหลังจาก jest.fn) เนื่องจาก IntelliJ กำลังบ่น มิฉะนั้นคำตอบนี้ช่วยฉันด้วยขอบคุณ! ใช้สิ่งนี้ใน package.json ของฉัน: "@ types / jest": "^ 24.0.3"
A. Masson

สิ่งที่ไม่jest.mock('./SomeClass');ในรหัสข้างต้น
Reza

11
ฮึ่มมันใช้ไม่ได้อีกต่อไปกับ TS เวอร์ชันล่าสุดและตลก 24 :(
Vincent

1
@Reza มันจำลองอัตโนมัติjestjs.io/docs/en/es6-class-mocks#automatic-mock
Bruce Lee

6
<jest.Mock<SomeClass>>SomeClassแสดงออกคือการผลิตผิดพลาด 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)
the21st

62

ใช้ตัวmockedช่วยจากts-jestlike อธิบายไว้ที่นี่

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

และถ้า

  • คุณใช้ tslint
  • ts-jest อยู่ในการพึ่งพาการพัฒนาของคุณ

เพิ่มกฎนี้ในtslint.json:"no-implicit-dependencies": [true, "dev"]


ตัวอย่างการใช้ts-jestและคลาสเพิ่มเติมมีดังนี้github.com/tbinna/ts-jest-mock-examplesและโพสต์นี้: stackoverflow.com/questions/58639737/…
Tobi

5
นี่เป็นคำตอบที่ดีกว่าคำตอบที่ได้รับการโหวตสูงสุด
fakeplasticandroid

@Tobi การทดสอบใน repo ล้มเหลว
Kreator

ขอขอบคุณที่ติดตาม @Kreator คุณเห็นปัญหาเดียวกันกับที่รายงานหรือไม่ ฉันไม่สามารถทำซ้ำปัญหาใด ๆ
Tobi

@ ครีเอเตอร์เพิ่งรวมพีอาร์ แจ้งให้เราทราบหากปัญหายังคงมีอยู่
Tobi

18

ฉันใช้รูปแบบจาก @ types / jest / index.d.ts เหนือประเภท def สำหรับ Mocked (บรรทัด 515):

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

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

2
ฉันค่อนข้างมั่นใจว่าคุณทำได้const myApi = new Api() as jest.Mocked<Api>;
snowfrogdev

4
@neoflash: ไม่ได้อยู่ในโหมดเข้มงวดใน TypeScript 3.4 - มันจะบ่นว่าประเภท Api ไม่ทับซ้อนกับjest.Mock<Api>. คุณต้องไปด้วยconst myApi = new Api() as any as jest.Mock<Api>และฉันจะบอกว่าข้อข้างบนดูดีกว่าการยืนยันสองครั้งเล็กน้อย
paolostyle

@tuptus: โหมดเข้มงวดสดสำหรับ 3.4 หรือไม่ คุณมีลิงค์เกี่ยวกับเรื่องนี้หรือไม่?
elmpp

@elmpp: ไม่แน่ใจว่าคุณหมายถึงอะไร โดย "โหมดเข้มงวด" ฉันหมายถึงมี"strict": trueใน tsconfig.json ปกนี้สิ่งที่ชอบnoImplicitAny, strictNullChecksฯลฯ ดังนั้นคุณจึงไม่ต้องตั้งค่าให้เป็นจริงสำหรับพวกเขาที
paolostyle

ฉันไม่เข้าใจ เหตุใดคุณจึงใช้วิธีการของอินสแตนซ์เดียวเท่านั้นเช่นmyApi? มันจะไม่ทำให้อินสแตนซ์อื่น ๆ ทั้งหมดเริ่มต้นโดยคลาสApiภายในโมดูลที่กำลังทดสอบใช่ไหม?
Ivan Wang

14

มีสองวิธีแก้ปัญหาทั้งสองแบบคือการหล่อฟังก์ชันที่ต้องการ

1) ใช้ jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

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

2) ใช้ jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

ไม่มีความแตกต่างระหว่างสองโซลูชันนี้ อันที่สองสั้นกว่าและฉันขอแนะนำให้ใช้อันนั้น

โซลูชันการหล่อทั้งสองช่วยให้สามารถเรียกใช้ฟังก์ชันเยาะเย้ยเยาะเย้ยในmockMyFunctionlike mockReturnValueหรือmockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction สามารถใช้งานได้ตามปกติ

expect(mockMyFunction).toHaveBeenCalledTimes(1);

7

นักแสดง as jest.Mock

เพียงแค่แคสต์ฟังก์ชันที่jest.Mockควรทำตามเคล็ดลับ:

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


6

นี่คือสิ่งที่ฉันทำกับjest@24.8.0และts-jest@24.0.2 :

ที่มา:

class OAuth {

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

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

ทดสอบ:

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

นี่คือวิธีการเยาะเย้ยคลาสที่ไม่ใช่ค่าเริ่มต้นและเป็นวิธีการคงที่:

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

ต่อไปนี้ควรเป็นการแปลงบางประเภทจากประเภทของชั้นเรียนของคุณเป็นjest.MockedClassหรืออะไรทำนองนั้น แต่มักจะลงเอยด้วยข้อผิดพลาด ดังนั้นฉันจึงใช้มันโดยตรงและได้ผล

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

แต่ถ้าเป็นฟังก์ชั่นคุณสามารถล้อเลียนและสนทนาประเภทนี้ได้

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

ฉันพบสิ่งนี้ใน@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);
*/

หมายเหตุ:เมื่อคุณทำconst mockMyFunction = myFunctionแล้วmockFunction.mockReturnValue('foo')คุณเป็นคนที่เปลี่ยนแปลงmyFunctionเช่นกัน

ที่มา: https://github.com/DefininiteTyped/DefininiteTyped/blob/master/types/jest/index.d.ts#L1089


1

ใช้as jest.Mockและไม่มีอะไรอื่น

วิธีที่รัดกุมมากที่สุดของการเยาะเย้ยโมดูลส่งออกเป็นdefaultใน jest.MockTS-ล้อเล่นว่าฉันจะคิดว่ามันเดือดลงไปหล่อโมดูลที่เป็น

รหัส:

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

สิทธิประโยชน์:

  • ไม่จำเป็นต้องอ้างถึงdefaultคุณสมบัติที่ใดก็ได้ในรหัสทดสอบ - คุณอ้างอิงชื่อฟังก์ชันที่ส่งออกจริงแทน
  • คุณสามารถใช้เทคนิคเดียวกันในการล้อเลียนการส่งออกที่มีชื่อ
  • ไม่มี* asในใบแจ้งยอดการนำเข้า
  • ไม่มีการหล่อที่ซับซ้อนโดยใช้typeofคำหลัก
  • ไม่มีการอ้างอิงพิเศษเช่นmocked.

0

ไลบรารีล่าสุดแก้ปัญหานี้ด้วยปลั๊กอิน babel: https://github.com/userlike/joke

ตัวอย่าง:

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

ระวังdepและmockReturnValueOnceปลอดภัยอย่างเต็มที่ ด้านบน tsserver รับทราบว่าdepencencyถูกนำเข้าและได้รับมอบหมายdepให้ทำการ refactor อัตโนมัติทั้งหมดที่ tsserver รองรับก็จะทำงานได้เช่นกัน

หมายเหตุ: ฉันดูแลห้องสมุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.