Как издеваться над объектом окна JavaScript с помощью Jest?

106

Мне нужно протестировать функцию, которая открывает новую вкладку в браузере

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

Я хотел бы поиздеваться над openфункцией окна, чтобы я мог убедиться, что в openфункцию передается правильный URL-адрес .

Используя 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]

Что мне делать с тестовым примером? Любые предложения или подсказки приветствуются.

Дэнни
источник

Ответы:

84

Вместо 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-слияния в Трэвисе ... есть идеи?
Alex JM
@AlexJM у вас такая же проблема? Могу поделиться, как вы издеваетесь над оконным объектом?
Danny
1
Я просто определяю window.property в своих тестах
maracuja-juice
@ Андреас есть ли способ издеваться над окном как с неопределенным stackoverflow.com/questions/59173156/…
ДИЛИП ТОМАС 05
Благодарность! После нескольких часов, мне просто нужно , чтобы изменения windowдляglobal
SrAxi
59

У меня сработал следующий метод. Такой подход позволил мне протестировать код, который должен работать как в браузере, так и в Node, поскольку он позволил мне установить 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();
});
tvsbrent
источник
Это намного лучше, Object.definePropertyпоскольку это позволяет не влиять на другие тесты при насмешке.
Сергей
10

Мы также можем определить его, используя globalвsetupTests

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

И назовите это using globalв реальном тесте:

// yourtest.test.js
it('correct url is called', () => {
    statementService.openStatementsReport(111);
    expect(global.open).toBeCalled();
});
Пох Зи Как
источник
7

В Jest есть несколько способов имитировать глобальные объекты:

  1. Используйте mockImplementationподход (наиболее похожий на Jest), но он будет работать только для тех переменных, которые имеют некоторую реализацию по умолчанию 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. Присвойте значение непосредственно глобальному свойству, наиболее просто, но может вызвать сообщения об ошибках для некоторых windowпеременных, например window.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}`);
  }
}
Jmarceli
источник
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();
});
абхишек хандаит
источник
5

Я нашел простой способ: удалить и заменить

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
  });
});
Джи Мок
источник
4

Если это похоже на проблему с расположением окон на https://github.com/facebook/jest/issues/890 , вы можете попробовать [скорректировано]

delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();
serv-inc
источник
3

В моем компоненте мне нужен доступ 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;
});
Алонад
источник
3

Я непосредственно назначая jest.fn()к window.open.

window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','__blank')
Сагар
источник