วิธีการจำลองวัตถุหน้าต่าง JavaScript โดยใช้ Jest?


112

ฉันต้องการทดสอบฟังก์ชันที่เปิดแท็บใหม่ในเบราว์เซอร์

openStatementsReport(contactIds) {
  window.open(`a_url_${contactIds}`);
}

ฉันต้องการจำลองopenฟังก์ชันของหน้าต่างเพื่อตรวจสอบว่า URL ที่ถูกต้องถูกส่งไปยังopenฟังก์ชัน

ใช้ Jest ฉันไม่รู้ว่าจะล้อเลียนไฟล์window. ฉันพยายามตั้งค่าwindow.openด้วยฟังก์ชันจำลอง แต่วิธีนี้ไม่ได้ผล ด้านล่างนี้เป็นกรณีทดสอบ

it('correct url is called', () => {
  window.open = jest.fn();
  statementService.openStatementsReport(111);
  expect(window.open).toBeCalled();
});

แต่มันทำให้ฉันมีข้อผิดพลาด

expect(jest.fn())[.not].toBeCalled()

jest.fn() value must be a mock function or spy.
    Received:
      function: [Function anonymous]

ฉันควรทำอย่างไรกับกรณีทดสอบ ข้อเสนอแนะหรือคำแนะนำใด ๆ จะได้รับการชื่นชม

คำตอบ:


91

แทนที่จะwindowใช้global

it('correct url is called', () => {
  global.open = jest.fn();
  statementService.openStatementsReport(111);
  expect(global.open).toBeCalled();
});

คุณยังสามารถลอง

const open = jest.fn()
Object.defineProperty(window, 'open', open);

3
พยายามแล้ว แต่ไม่ได้ผลสำหรับฉัน กรณีของฉันเป็นเรื่องแปลกการล้อเลียนทำงานในพื้นที่ แต่ไม่ใช่สำหรับการรวม PR ใน Travis ... มีความคิดอย่างไร
Alex JM

@AlexJM คุณมีปัญหาเดียวกันหรือไม่? มีใจที่จะแบ่งปันคุณล้อเลียนวัตถุหน้าต่างได้อย่างไร?
danny

1
ฉันแค่กำหนด window.property ในการทดสอบของฉัน
maracuja-juice

@ Andreas มีวิธีใดบ้างที่จะล้อเลียนหน้าต่างในแบบที่ไม่ได้กำหนดstackoverflow.com/questions/59173156/…
DILEEP THOMAS

ขอบคุณ! หลังจากเวลาผ่านไปฉันต้องเปลี่ยนwindowสำหรับglobal
SrAxi

70

วิธีการที่ใช้ได้ผลสำหรับฉันมีดังต่อไปนี้ วิธีนี้ทำให้ฉันสามารถทดสอบโค้ดบางอย่างที่ควรใช้งานได้ทั้งในเบราว์เซอร์และในโหนดเนื่องจากอนุญาตให้ตั้งค่าwindowเป็นundefinedที่จะ

นี่คือ Jest 24.8 (ฉันเชื่อว่า):

let windowSpy;

beforeEach(() => {
  windowSpy = jest.spyOn(window, "window", "get");
});

afterEach(() => {
  windowSpy.mockRestore();
});

it('should return https://example.com', () => {
  windowSpy.mockImplementation(() => ({
    location: {
      origin: "https://example.com"
    }
  }));

  expect(window.location.origin).toEqual("https://example.com");
});

it('should be undefined.', () => {
  windowSpy.mockImplementation(() => undefined);

  expect(window).toBeUndefined();
});

1
ดีกว่ามากObject.definePropertyเนื่องจากสิ่งนี้ช่วยให้ไม่ส่งผลกระทบต่อการทดสอบอื่น ๆ เมื่อล้อเลียน
Sergey

นี่ควรเป็นคำตอบที่ยอมรับได้เนื่องจากมันล้อเลียน / สอดแนมแทนที่จะเปลี่ยนคุณสมบัติทั่วโลกจริง
2490003

10

เรายังสามารถกำหนดโดยใช้globalในsetupTests

// setupTests.js
global.open = jest.fn()

และเรียกใช้globalในการทดสอบจริง:

// yourtest.test.js
it('correct url is called', () => {
    statementService.openStatementsReport(111);
    expect(global.open).toBeCalled();
});

7

มีสองวิธีในการล้อเลียนลูกโลกใน Jest:

  1. ใช้mockImplementationวิธีการ (ล้อเล่นมากที่สุดเช่นเดียว) แต่มันจะทำงานเฉพาะสำหรับตัวแปรเหล่านั้นซึ่งมีการดำเนินการเริ่มต้นบางอย่างให้โดยjsdom, window.openเป็นหนึ่งของพวกเขา
test('it works', () => {
  // setup
  const mockedOpen = jest.fn();
  // without making a copy you will have a circular dependency problem
  const originalWindow = { ...window };
  const windowSpy = jest.spyOn(global, "window", "get");
  windowSpy.mockImplementation(() => ({
    ...originalWindow, // in case you need other window properties to be in place
    open: mockedOpen
  }));

  // tests
  statementService.openStatementsReport(111)
  expect(mockedOpen).toBeCalled();

  // cleanup
  windowSpy.mockRestore();
});
  1. ค่ากำหนดโดยตรงไปยังสถานที่ให้บริการทั่วโลกส่วนใหญ่ตรงไปตรงมา แต่อาจก่อให้เกิดข้อผิดพลาดบางตัวแปรเช่นwindowwindow.href
test('it works', () => {
  // setup
  const mockedOpen = jest.fn();
  const originalOpen = window.open;
  window.open = mockedOpen;

  // tests
  statementService.openStatementsReport(111)
  expect(mockedOpen).toBeCalled();

  // cleanup
  window.open = originalOpen;
});
  1. อย่าใช้ลูกโลกโดยตรง (ต้องมีการปรับโครงสร้างเล็กน้อย)

แทนที่จะใช้ค่าส่วนกลางโดยตรงการอิมพอร์ตจากไฟล์อื่นอาจจะสะอาดกว่าดังนั้นการเยาะเย้ยจะกลายเป็นเรื่องเล็กน้อยกับ Jest

./test.js

jest.mock('./fileWithGlobalValueExported.js');
import { windowOpen } from './fileWithGlobalValueExported.js';
import { statementService } from './testedFile.js';

// tests
test('it works', () => {
  statementService.openStatementsReport(111)
  expect(windowOpen).toBeCalled();
});

./fileWithGlobalValueExported.js

export const windowOpen = window.open;

./testedFile.js

import { windowOpen } from './fileWithGlobalValueExported.js';
export const statementService = {
  openStatementsReport(contactIds) {
    windowOpen(`a_url_${contactIds}`);
  }
}

7

ฉันพบวิธีง่ายๆในการทำ: ลบและแทนที่

describe('Test case', () => {
  const { open } = window;

  beforeAll(() => {
    // Delete the existing
    delete window.open;
    // Replace with the custom value
    window.open = jest.fn();
    // Works for `location` too, eg:
    // window.location = { origin: 'http://localhost:3100' };
  });

  afterAll(() => {
    // Restore original
    window.open = open;
  });

  it('correct url is called', () => {
    statementService.openStatementsReport(111);
    expect(window.open).toBeCalled(); // Happy happy, joy joy
  });
});

7

ในองค์ประกอบของฉันฉันต้องการเข้าถึงwindow.location.searchนี่คือสิ่งที่ฉันทำในการทดสอบความตลกขบขัน:

Object.defineProperty(global, "window", {
  value: {
    location: {
      search: "test"
    }
  }
});

ในกรณีที่คุณสมบัติของหน้าต่างต้องแตกต่างกันในการทดสอบที่แตกต่างกันเราสามารถใส่การจำลองหน้าต่างลงในฟังก์ชันและทำให้สามารถเขียนได้เพื่อแทนที่การทดสอบที่แตกต่างกัน:

function mockWindow(search, pathname) {
  Object.defineProperty(global, "window", {
    value: {
      location: {
        search,
        pathname
      }
    },
    writable: true
  });
}

และรีเซ็ตหลังจากการทดสอบแต่ละครั้ง

afterEach(() => {
  delete global.window.location;
});

5

คุณสามารถลองสิ่งนี้:

import * as _Window from "jsdom/lib/jsdom/browser/Window";

window.open = jest.fn().mockImplementationOnce(() => {
    return new _Window({ parsingMode: "html" });
});

it("correct url is called", () => {
    statementService.openStatementsReport(111);
    expect(window.open).toHaveBeenCalled();
});


4

ฉันโดยตรงกำหนดที่จะjest.fn()window.open

window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','__blank')

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