Jest: No se puede cambiar window.location usando Object.defineProperty

Creado en 19 dic. 2017  ·  78Comentarios  ·  Fuente: facebook/jest

¿Quieres solicitar una _ función_ o informar de un _ error_? Reportar un error

¿Cuál es el comportamiento actual?

Llamar a lo siguiente desde dentro de una suite de pruebas:

Object.defineProperty(location, "hostname", {
  value: "example.com",
  writable: true
});

arroja el siguiente error:

    TypeError: Cannot redefine property: hostname
        at Function.defineProperty (<anonymous>)

¿Cuál es el comportamiento esperado?

El código no debería generar una excepción y window.location.hostname === "example.com" debería evaluarse como verdadero.

Por lo que parece, jsdom ahora configura window.location para que sea imperdonable . La única forma de cambiar los valores dentro de window.location es usar reconfigure , pero (según # 2460) Jest no expone jsdom para que las pruebas jueguen.

Proporcione su configuración exacta de Jest y mencione su Jest, nodo,versión y sistema operativo de yarn / npm.

Versión de broma : 22.0.1
Versión de nodo : 8.6.0
Versión de hilo : 1.2.0
SO : macOS High Sierra 10.13.2

Discussion Wontfix

Comentario más útil

He publicado un nuevo paquete en npm llamado jest-environment-jsdom-global , que puede ayudar con los problemas que algunas personas están teniendo con Object.defineProperty .

Todos 78 comentarios

Tengo un problema similar. Puede crear su propio JSDOMEnvironment y exponer el objeto jsdom al global de esta manera.

const JSDOMEnvironment = require('jest-environment-jsdom');

module.exports = class CustomizedJSDomEnvironment extends JSDOMEnvironment {
  constructor(config) {
    super(config);
    this.global.jsdom = this.dom;
  }

  teardown() {
    this.global.jsdom = null;
    return super.teardown();
  }
};

Y luego puede llamar a jsdom.reconfigure en su caso de prueba como desee

Esa es una buena solución, ¡gracias por compartir!

Deberías devolver super.teardown(); ya que es una promesa, por cierto

Perfecto, @oliverzy , lo intentaré. ¡Gracias!

¿Existe un lugar apropiado para documentar esto? Parece ser una pregunta que surge con bastante frecuencia; con suerte, los problemas futuros podrían reducirse si esto se integrara en los documentos.

Esta solución no funcionó del todo .

Dentro de nuestros archivos de prueba, parece que global está configurado para ser el objeto window JSDom.

En otras palabras, dentro de una suite de prueba, global es lo mismo que window , pero dentro de la clase que se extiende JSDOMEnvironment , global proviene del entorno de Node.

Como resultado, tener esto:

describe("test suite", () => {
  it("should not fail", () => {
    global.jsdom.reconfigure({
      url: "https://www.example.com/"
    });
  });
});

falla porque global.jsdom no está definido.

Lo solucioné haciendo esto, pero no estoy muy preocupado por eso.

const JSDOMEnvironment = require("jest-environment-jsdom");

module.exports = class JSDOMEnvironmentGlobal extends JSDOMEnvironment {
  constructor(config) {
    super(config);

    this.dom.window.jsdom = this.dom;
  }
};

Con este entorno, global.jsdom dentro de un conjunto de pruebas es igual a this.dom , y el conjunto de pruebas anterior funciona.

Para mí, se siente como configurar jsdom para que sea una propiedad propia window objeto está destinado a desmoronarse eventualmente, ¿hay una manera más limpia de hacerlo?

debe escribir jsdom lugar de global.jsdom en sus pruebas.

@oliverzy ¿ Te gusta esto?

describe("test suite", () => {
  it("should not fail", () => {
    jsdom.reconfigure({
      url: "https://www.example.com/"
    });
  });
});

Eso arroja jsdom is not defined , pero puedo estar malinterpretando.

@ simon360 Configure testEnvironment con el código de @oliverzy , consulte https://facebook.github.io/jest/docs/en/configuration.html#testenvironment -string

@danielbayerlein mi configuración de Jest tiene esto:

"testEnvironment": "@wel-ui/jest-environment-jsdom-global"

donde @wel-ui/jest-environment-jsdom-global es el nombre de un paquete en nuestro monorepo. Sin embargo, el entorno se está utilizando correctamente porque la solución que establece jsdom en window funciona como se esperaba.

Por cierto, ¿alguien sabe por qué la solución original no funciona en la nueva versión?
Éste:

Object.defineProperty(location, "hostname", {
  value: "example.com",
  writable: true
});

@modestfake hemos actualizado de JSDOM @ 9 a JSDOM @ 11 , supongo que cambiaron la forma en que se define la variable

@SimenB Lo tengo. Acabo de encontrar una descripción del método jsdom reconfigure .

La propiedad superior en la ventana está marcada como [No falsificable] en la especificación, lo que significa que es una propiedad propia no configurable y, por lo tanto, no puede ser anulada o sombreada por el código normal que se ejecuta dentro de jsdom, incluso usando Object.defineProperty.

Agregué un nuevo repositorio para demostrar este comportamiento. ¿Alguien puede reproducirlo clonando localmente?

https://github.com/simon360/test-environment-for-jest

@ simon360 reproducido
image

@ simon360 que he encontrado. Se ha perdido la palabra clave this al definir global.jsdom :

const JSDOMEnvironment = require("jest-environment-jsdom");

module.exports = class JSDOMEnvironmentGlobal extends JSDOMEnvironment {
  constructor(config) {
    super(config);

    this.global.jsdom = this.dom;
  }

  teardown() {
    this.global.jsdom = null;

    return super.teardown();
  }
};

¿Qué pasa con location.search ? No encontré ninguna mención al respecto https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring -the-jsdom-with-reconfiguresettings

@andrewBalekha ¿Qué pasa con esto?

jsdom.reconfigure({
  url: 'https://www.example.com/endpoint?queryparam1=15&queryparam2=test'
});

Gracias @modestfake , ¡perdón por el tonto error!

Ok, lo veo ahora: this.global en un objeto de entorno Jest se establece como global en un archivo de prueba Jest. Eso tiene sentido, ¡gracias por ayudarme a superarlo! Si hay suficiente interés, podría empaquetar la versión reparada de ese repositorio y ponerla en npm como jest-environment-jsdom-global .

Sin embargo, espero que haya una forma más limpia de hacer esto en Jest en el futuro. Esta no es una forma de baja fricción para cambiar window.location -

¿Podría haber un nuevo docblock, como el que hay para @jest-environment ? Por ejemplo...

/**
 * @jest-url https://www.example.com/
 */

O tal vez JSDom pueda exponerse en una parte especial del objeto jest , algo como:

jest.environment.jsdom.reconfigure({
  url: "https://www.example.com/"
});

(que tendría el beneficio adicional de poder cambiar window.top )

Hemos fusionado el número 5003 ahora. poder agregarlo como un docblock podría tener sentido, no estoy seguro. @cpojer? También podríamos desaprobar testUrl , ya que se puede proporcionar a través de esa nueva opción.

Si hay suficiente interés, podría empaquetar la versión reparada de ese repositorio y ponerla en npm como jest-environment-jsdom-global .

Creo que tiene sentido en cualquier caso, ya que hace más que solo permitirle establecer la URL: expone el JSDOM completo al entorno

@andrewBalekha Object.defineProperty(location, 'search', { ...options }); arroja el mismo error que window.location . Sin embargo, gracias por la sugerencia.

Object.defineProperty (window.location, 'href', {
set: newValue => {currentUrl = newValue; },
});
Tenía esto en versiones anteriores y ahora arroja un error.
Si agrego escribible: verdadero
lanza otra excepción que no puedo especificar tanto el descriptor de acceso como el de escritura

He publicado un nuevo paquete en npm llamado jest-environment-jsdom-global , que puede ayudar con los problemas que algunas personas están teniendo con Object.defineProperty .

¿Alguien tiene una solución alternativa para { writable: true } ?

Por ejemplo:

Object.defineProperty(window.location, 'href', { writable: true })

...

Object.defineProperty(window.location, 'hash', { writable: true })

...

Object.defineProperty(window.location, 'search', { writable: true })

@danielbayerlein leyó este hilo. Necesita crear un entorno personalizado. El mensaje anterior contiene una URL con un ejemplo

@modestfake Ya leí este hilo y https://github.com/facebook/jest/issues/5124#issuecomment -352749005 funciona bien. Pero tengo otro caso de uso. Con Jest 21.xx he configurado Object.defineProperty(window.location, 'href', { writable: true }) sin la URL, solo { writable: true } . Si configuro la URL, la prueba no tiene sentido.

@danielbayerlein ¿cuál es el caso de uso para que se pueda escribir pero no anularlo en realidad? Tal vez comprender esto pueda ayudarme a encontrar una solución

Tengo una función que cambia la URL.

routing.js

...

export function redirectToErrorPage () {
  window.location.href = '/error.html'
}

...

routing.test.js

test('redirect to the error page', () => {
  ...
  expect(window.location.href).toBe('/error.html')
  ...
})

Con Jest 21.xx he configurado Object.defineProperty(window.location, 'href', { writable: true })

Recomiendo cambiar a window.location.assign , de esa manera puedes burlarte de la función.

@ simon360 ¡ Funciona a las

solía

history.pushState({}, "page 2", "/bar.html");

junto con testURL en la configuración de broma

La parte de la ubicación no es el único problema. No puedo importar jsdom separado para llamar a jsdom.reconfigureWindow (porque esa función ya no existe en la última versión de jsdom). Estaba haciendo esto para probar el código que se ejecuta de manera diferente si window !== top . Ya no hay forma de lograr esto en la última versión de jest, que usa una versión más reciente de jsdom.

@andyearnshaw el jsdom.reconfigure tiene un miembro windowTop en su objeto.

Si está utilizando el entorno JSDOM ( jest-environment-jsdom-global ) que vinculé arriba, podría hacer:

jsdom.reconfigure({
  windowTop: YOUR_VALUE
});

en sus pruebas para simular un valor superior.

Estoy usando eso y funciona muy bien, ¡gracias!

El martes 30 de enero de 2018, 13:40 simon360, [email protected] escribió:

@andyearnshaw https://github.com/andyearnshaw el jsdom.reconfigure
El método tiene un miembro windowTop en su objeto.

Si está utilizando el entorno JSDOM (jest-environment-jsdom-global) I
vinculado anteriormente, podría hacer:

jsdom.reconfigure ({
windowTop: YOUR_VALUE
});

en sus pruebas para simular un valor superior.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/facebook/jest/issues/5124#issuecomment-361595999 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/ABvdEzCLlWtzr0udscL0C6KUxpgXHZRhks5tPxvJgaJpZM4RGN7C
.

Usar window.history.pushState y testUrl funcionó para mí

https://github.com/facebook/jest/issues/5124#issuecomment -359411593

Voy a cerrar esto porque no hay nada que hacer por parte de Jest y existen soluciones alternativas (¡siéntase libre de continuar la discusión!)

La actualización de jsdom v8.5.0 a v11.6.2 resolvió el problema para mí. Entonces mi package.json incluye:

"jest": "^21.2.1",
"jest-cli": "^21.2.1",
"jsdom": "^11.6.2",

Se rompe si actualizo jest y jest-cli a v22.2.2.

@ andr-3-w ¿Alguna razón en particular por la que tiene jsdom en su package.json? Viene incluido con Jest.

@SimenB , buena pregunta, no hay necesidad de jsdom en nuestro package.json . ¡Gracias!

Entonces, sin usar jsdom directamente, aquí está la solución que se me ocurrió para mis cosas:

Funciona para Jest 21.2.1 (probé en ese):

Vaya a la configuración de Jest (por ejemplo, usaré package.json):

"jest": { "testURL": "http://localhost" }

Ahora podrá cambiar el objeto window.location y luego podrá configurar la URL a lo que quiera durante las pruebas.

it('Should set href url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = 'http://localhost/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'href', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.href);
});

it('Should set pathname url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = '/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'pathname', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.pathname);
});

Ojalá esto ayude a alguien.

Deja de publicar esto, no funciona en broma ":" ^ 22.4.2 "

@UserNT Noté la versión en la que funciona aquí y la uso ampliamente en trajes de prueba de producción. Si no funciona en versiones más nuevas, lo siento, proponga su propia solución en lugar de simplemente atacar al azar.

Solo para completar, ya que la solución está varada en medio de este hilo ...

La solución de @ petar-prog91 funcionará en Jest 21, pero no en Jest 22 con el jsdom actualizado.

Para ejecutar el último Jest, use algo como jest-environment-jsdom-global (divulgación completa, este es mi paquete) para exponer el objeto jsdom y use jsdom.reconfigure , que tendrá la mismo efecto (o, al menos, similar).

https://github.com/facebook/jest/issues/5124#issuecomment -359411593 también funciona en broma 22 para mí

@ simon360 Hola, ¿qué debo hacer si necesito hackear el setter de location.href ?
Mis pruebas anteriores tienen muchas pruebas como esta y ahora todas fallaron ...

const setHrefMockFn = jest.fn();
beforeAll(() => {
  Object.defineProperty(location, "href", {
    get: () => "https://xxxxx",
    set: setHrefMockFn
  });
});
it("xxx", () => {
  //...
  expect(setHrefMockFn.mock.calls[0][0]).toBe(xxx);
});

@ simon360, ¿ puede darme un ejemplo de cómo actualizar la prueba como se muestra a continuación con su biblioteca?

beforeAll(() => {
  =======

  Object.defineProperty(window.location, 'href', {
    writable: true
  });
});

@abhijeetNmishra ¿has visto la documentación ? Creo que eso respondería a tu pregunta.

@ simon360 Sí, según mi comprensión de la documentación, se necesitan aproximadamente

jsdom.reconfigure({
      url: "https://www.example.com/"
    });

que anula la URL de forma global y no por prueba. ¡Por favor ayuda!

@abhijeetNmishra No estoy seguro de que este tema sea el mejor lugar para discutir. ¿Le importaría abrir un problema en el repositorio jest-environment-jsdom-global donde podamos solucionarlo? ¡Gracias!

@SimenB la solución alternativa indicada ("use jest-environment-jsdom-global ") se siente como una solución extremadamente subóptima para lo que obviamente es un problema muy común. Cualquiera que actualice a Jest 22 ahora necesita agregar una dependencia a ese paquete de terceros y (desde la perspectiva del usuario) reescribir algunas de sus pruebas. Esta es una regresión en el comportamiento de Jest.

¿Existe una solución para esto que podamos incorporar en el valor predeterminado jest-environment-jsdom ? Feliz de hacer un PR con su guía.

Es muy desafortunado que alguien tenga que atravesar tantos obstáculos solo para cambiar window.location.href. Acabo de comenzar a usar Jest y estoy a punto de reconsiderar mi elección de marco de prueba dado este problema. ¿Realmente no hay mejor solución que los horribles trucos sugeridos anteriormente?

@ydogandjiev no dude en contribuir al proyecto para resolver este problema. Recuerde que esto es de código abierto, por lo que atacar con comentarios como "inaceptable" y "ridículo" no ayuda a nadie.

@ msholty-fd Me encantaría ayudar si puedo. Dado que recién estoy comenzando con jest y jsdom, no estoy seguro de tener un conocimiento lo suficientemente profundo de estas bibliotecas como para saber exactamente cuál es la mejor ruta para mejorar esta experiencia. ¿Es esto algo que se aborda mejor en broma o en jsdom? Parece que una de estas bibliotecas hizo un cambio en algún momento que rompió el enfoque de Object.defineProperty; ¿Fue un cambio hecho en broma o en jsdom?

Así que aquí están todas las opciones que consideraría preferibles en función del número de líneas necesarias para cambiar window.location; ninguno de estos funciona actualmente:

  1. La configuración de href directamente no funciona porque jsdom arroja una excepción con el mensaje "Error: No implementado: navegación (excepto cambios de hash)":
    window.location.href = "https://www.example.com";
  1. El uso de Object.defineProperty no funciona porque JSDOM ha hecho que la propiedad de ubicación de la ventana sea [Unforgeable] :

    Object.defineProperty(window.location, "href", {
      value: "https://www.example.com",
      configurable: true
    });
    
  2. Crear una instancia de jsdom y configurarla no funciona porque jest parece usar su propia instancia que no está expuesta para un fácil acceso:

    import { JSDOM } from "jsdom";
    ...
    const dom = new JSDOM();
    dom.reconfigure({ url: "https://www.example.com" });
    

Hacer que las opciones 1 o 2 funcionen requeriría que jsdom retroceda en sus objetivos actuales de comportarse más como un navegador real. Por lo tanto, parece que la única opción que tenemos es facilitar la reconfiguración de la instancia de jsdom que usa jest. ¿Tendría sentido exponer esa instancia directamente en el objeto de broma global? es decir, permitir algo como esto:

jest.dom.reconfigure({ url: "https://www.example.com" });

Sigo pensando que hacer location.assign('some-url') es mejor que location.href = 'some-url' . Encuentro una llamada a función más explícita que la asignación, y puedes burlarte de la función

@SimenB en los casos en que el código está intentando _set_ location.href , sí, location.assign() es mejor. Pero si está probando un comportamiento que _reads_ location.href , location.assign() no resuelve el problema, especialmente porque location.assign() en JSDOM en realidad no hace nada.

La idea detrás de usar reconfigure es activar una ruta de código que solo se habilita cuando location.href se forma de una manera particular. En nuestro caso, tuvimos un código que cambió según el dominio actual: el código huele mal, sí, pero también es necesario, y la mejor manera de mitigar el código maloliente es tener dispositivos de prueba que capturen el comportamiento y aseguren que se mantenga en su lugar.

¿Hay alguna forma de usar esto con Enzyme y un componente montado para probar las redirecciones?

La siguiente prueba pasó antes de actualizar Jest:

''
it ('rutas a la ruta correcta', () => {

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'https://mysuperawesomesite.com/',
});

const component = mount(
  <App {...props} />
);

const link = component.find('.class');

link.simulate('click');

expect(window.location.href).toEqual('https://mysuperawesomesite.com/new');

});

After upgrading Jest and implementing [jest-environment-jsdom-global](https://www.npmjs.com/package/jest-environment-jsdom-global), I tried the following to no avail:

  ```
it('routes to correct route', () => {

    jsdom.reconfigure({
      url: 'https://mysuperawesomesite.com/',
    });

    const component = mount(
      <App {...props} />
    );

    const link = component.find('.class');

    link.simulate('click');

    expect(window.location.href).toEqual('https://mysuperawesomesite.com/new');
  });

(window.location.href sigue siendo igual a ' https://mysuperawesomesite.com/ ', no se cambió a ('https://mysuperawesomesite.com/new').

El evento de clic en el elemento no se redirige cuando se usa este método, y la redirección se produce al configurar window.location.href.

No está claro cómo probar esto correctamente o si las pruebas que previamente habían usado Object.defineProperty estaban mal construidas para empezar. Gracias de antemano por cualquier ayuda.

EDITAR: RESUELTO

Pude resolver esto usando window.location.assign (url) en lugar de window.location.href = href. Esto me permitió eliminar el método de asignación y probar si se estaba llamando correctamente. Vea abajo:

it('routes to correct route', () => {
    window.location.assign = jest.fn();

    const component = mount(
      <App {...props} />
    );

    const link = component.find('.class');

    link.simulate('click');

    expect(window.location.assign).toBeCalledWith('https://mysuperawesomesite.com/new');
    window.location.assign.mockRestore();
  });

@SimenB Hay una gran diferencia entre .assign y .href Puede leer en MDN. El primero tiene una restricción importante entre dominios. En mi código, quiero redirigir la página principal desde un iframe donde se está ejecutando mi código. Esos son dominios cruzados. Y la única forma en que puedo hacer esto es cambiando href .
Me encantaría que se volviera a abrir este problema, ya que no veo una solución actual y tendré que no probar esta función. Lo que obviamente apesta.

Estoy en el mismo barco que @soswow. Sería genial si pudiera proporcionar un mecanismo para anular la URL, ya que también eliminaré varias pruebas unitarias hasta que se restaure esta funcionalidad.

Me encantaría que se volviera a abrir este problema, ya que no veo una solución actual y tendré que no probar esta función. Lo que obviamente apesta.

No hay nada que podamos hacer por broma. Estoy seguro de que a jsdom le encantaría un PR que lo apoyara. https://github.com/jsdom/jsdom/issues/2112

Aquí tienes una solución simple que funciona.

describe('changing location', () => {
  const testURL = location.href;

  beforeEach(() => history.replaceState({}, 'Login', '/login'));
  afterEach(() => history.replaceState({}, 'Home', '/'));

  it('works', () => {
    expect(location.pathname).toBe('/login');
  });
});

@vastus Como señalé explícitamente, el problema está en los dominios cruzados . La API de historial no permitirá cambiar a un dominio diferente que yo recuerde.

Dado que location no se puede anular directamente en el objeto jsdom window , un enfoque posible es anularlo en un objeto derivado:

global.window = Object.create(window);
Object.defineProperty(window, 'location', {
  value: {
    href: 'http://example.org/'
  }
});

Utilizo esto para resolver el problema:

const windowLocation = JSON.stringify(window.location);
delete window.location;
Object.defineProperty(window, 'location', {
  value: JSON.parse(windowLocation)
});

Inspirado por @RubenVerborgh y annemarie35

Agregando un ejemplo de prueba location.search basado en la solución de @vastus :

  test('gets passed query param and returns it as a string if it exists', () => {
    history.replaceState({}, 'Test', '/test?customer=123');
    const customerId = getQueryParam('customer');
    expect(customerId).toBe('123');
  });

@RubenVerborgh Funciona a las

tratar:

window.history.pushState({}, null, '/pathname?k=v');

Una solución similar a @sahalsaad :
javascript
const oldWindow = window.location;
eliminar window.location;
window.location = {
... oldWindow,
// incluir cualquier sobrescritura personalizada, como el siguiente código auxiliar de sinon
reemplazar: sinon.stub (),
};

// haz tu magia

window.location = oldWindow;
`` ``

@sahalsaad gracias! Usé una variación de su solución para simular window.location.search:

const location = {
    ...window.location,
    search: queryString,
};
Object.defineProperty(window, 'location', {
    writable: true,
    value: location,
});

probablemente una mejor solución:

import { URL } from 'whatwg-url';

const location = new URL(window.location.href);
location.assign = jest.fn()
location.replace = jest.fn()
location.reload = jest.fn()

delete window.location
window.location = location

Resolví mi problema usando la solución dada por @kdelmonte , tuve que burlarme de la variable window.location.search . entonces he usado

window.history.pushState({}, null, '?skuId=1234')

Dado que location no se puede anular directamente en el objeto jsdom window , un enfoque posible es anularlo en un objeto derivado:

global.window = Object.create(window);
Object.defineProperty(window, 'location', {
  value: {
    href: 'http://example.org/'
  }
});

tu respuesta es la única que funciona para mi situación, ¡gracias!

Dado que location no se puede anular directamente en el objeto jsdom window , un enfoque posible es anularlo en un objeto derivado:

global.window = Object.create(window);
Object.defineProperty(window, 'location', {
  value: {
    href: 'http://example.org/'
  }
});

tu respuesta es la única que funciona para mi situación, ¡gracias!

Esto ya no funciona 😢

Dado que location no se puede anular directamente en el objeto jsdom window , un enfoque posible es anularlo en un objeto derivado:

global.window = Object.create(window);
Object.defineProperty(window, 'location', {
  value: {
    href: 'http://example.org/'
  }
});

tu respuesta es la única que funciona para mi situación, ¡gracias!

Esto ya no funciona 😢

No funciona para mi tampoco

Resolví esto haciendo:

delete window.location
window.location = {
  href: 'http://example.org/,
}

Estoy usando la siguiente simulación como una utilidad para trabajar con Location

export class MockLocation extends URL implements Location {
  ancestorOrigins: any = []
  toString = jest.fn().mockImplementation(() => this.toString())
  assign = jest.fn(href => this.href = href)
  replace = jest.fn(href => this.href = href)
  reload = jest.fn()

  constructor(
    url: string = 'http://mock.localhost',
  ) {
    super(url)
  }

  onWindow(window: Window) {
    Object.defineProperty(window, 'location', { 
      writable: true,
      value: this
    });
    return this
  }
}

Entonces en mis pruebas

let location: MockLocation

beforeEach(() => {
    location = new MockLocation(MOCK_PARTNER_URL).onWindow(window)
})

Me encontré tropezando con objetos complicados como estos todo el tiempo y creé una función auxiliar flexible:

export const safelyStubAndThenCleanup = (target, method, value) => {
  const original = target[method]
  beforeEach(() => {
    Object.defineProperty(target, method, { configurable: true, value })
  })
  afterEach(() => {
    Object.defineProperty(target, method, { configurable: true, value: original })
  })
}

Y luego uso:

describe('when on /pages', () => {
  safelyStubAndThenCleanup(window, 'location', { pathname: '/pages' })

  it('should do something neat', () => { /* ... */ })
})

Y puede copiar lo que quiera: pathname , href , etc ... Esto le brinda el beneficio adicional gratuito de limpieza.

La clave es que no puede meterse con location sí, así que simplemente cambie location por una falsificación y luego vuelva a colocarla cuando termine la prueba.

Como puedo ver en mi sesión de depuración, global.location se implementa a través de getter pero no una propiedad simple. ¿No sería más seguro redefinirlo así?

let originalLocationDescriptor;
beforeAll(() => {
  originalLocationDescriptor = Object.getOwnPropertyDescriptor(global, 'location');
  delete global.location;
  global.location = {};
});
afterAll(() => {
  Object.defineProperty(global, 'location', originalLocationDescriptor);
}):

Aunque es difícil imaginar por qué querría usar el global.location , parece un poco más correcto.
Y este código funciona bien para mí, por supuesto. Solo accedo a location.pathname , pero este objeto puede extenderse fácilmente con algo de jest.fn() si es necesario.

Esto ha funcionado para mí, usando broma 26.5

function stubLocation(location) {
  beforeEach(() => {
    jest.spyOn(window, "location", "get").mockReturnValue({
      ...window.location,
      ...location,
    });
  });
}

stubLocation({ pathname: "/facebook/jest/issues/5124" });

test("mocks location prop", () => {
  expect(window.location.pathname).toEqual("/facebook/jest/issues/5124");
});

Agregando un ejemplo de prueba location.search basado en la solución de @vastus :

  test('gets passed query param and returns it as a string if it exists', () => {
    history.replaceState({}, 'Test', '/test?customer=123');
    const customerId = getQueryParam('customer');
    expect(customerId).toBe('123');
  });

Esto funcionó exactamente bien para el problema que estaba teniendo

¿Fue útil esta página
0 / 5 - 0 calificaciones