¿Cómo simular el objeto de la ventana de JavaScript usando Jest?

105

Necesito probar una función que abre una nueva pestaña en el navegador

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

Me gustaría burlarme de la openfunción de la ventana para poder verificar que se haya pasado la URL correcta a la openfunción.

Usando Jest, no sé cómo burlarme del window. Intenté configurarlo window.opencon una función simulada, pero de esta manera no funciona. A continuación se muestra el caso de prueba

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

pero me da el error

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

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

¿Qué debo hacer con el caso de prueba? Se agradece cualquier sugerencia o sugerencia.

danny
fuente

Respuestas:

83

En lugar de windowusarglobal

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

también podrías intentar

const open = jest.fn()
Object.defineProperty(window, 'open', open);
Andreas Köberle
fuente
3
Intenté esto pero no funcionó para mí. Mi caso es extraño, la burla funciona localmente pero no para una fusión de relaciones públicas en Travis ... ¿alguna idea?
Alex JM
@AlexJM, ¿tienes el mismo problema? ¿Te importa compartir cómo te burlas del objeto de la ventana?
danny
1
Acabo de definir window.property en mis pruebas
maracuja-juice
@ Andreas ¿hay alguna manera de burlarse de la ventana como no definidos stackoverflow.com/questions/59173156/...
DILEEP THOMAS
¡Gracias! Después de horas, solo necesitaba cambiarme windowporglobal
SrAxi
58

Un método que funcionó para mí fue el siguiente. Este enfoque me ha permitido poner a prueba un código que debe trabajar tanto en el navegador y en el nodo, ya que permitió que fije windowa undefined.

Esto fue con Jest 24.8 (creo):

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
fuente
Eso es mucho mejor, Object.definePropertyya que esto permite no afectar otras pruebas al burlarse.
Sergey
10

También podemos definirlo usando globalensetupTests

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

Y llámelo usando globalen la prueba real:

// yourtest.test.js
it('correct url is called', () => {
    statementService.openStatementsReport(111);
    expect(global.open).toBeCalled();
});
Poh Zi Cómo
fuente
7

Hay un par de formas de simular globales en Jest:

  1. Use el mockImplementationenfoque (la mayoría de las formas de Jest), pero funcionará solo para aquellas variables que tienen alguna implementación predeterminada proporcionada por jsdom, window.openes una de ellas:
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. Asignar valor directamente a la propiedad global, la más sencilla, pero puede generar mensajes de error para algunas windowvariables, por ejemplo 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. No use globales directamente (requiere un poco de refactorización)

En lugar de usar el valor global directamente, podría ser más limpio importarlo desde otro archivo, por lo que burlarse se volverá trivial con 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
fuente
5

Puedes probar esto:

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();
});
abhishek khandait
fuente
5

Encontré una manera fácil de hacerlo: eliminar y reemplazar

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
  });
});
Jee Mok
fuente
4

Si es similar al problema de ubicación de la ventana en https://github.com/facebook/jest/issues/890 , puede intentar [ajustado]

delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();
serv-inc
fuente
3

En mi componente al que necesito acceso window.location.search, esto es lo que hice en la prueba de broma:

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

En caso de que las propiedades de la ventana deban ser diferentes en diferentes pruebas, podemos poner la simulación de ventana en una función y hacerla modificable para anularla para diferentes pruebas:

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

Y reiniciar después de cada prueba

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

Estoy asignando directamente jest.fn()a window.open.

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