w3schools property img attribute javascript reactjs unit-testing asynchronous jestjs

javascript - property - title label html



Cómo simular una llamada de función asíncrona en otra clase (3)

Tengo el siguiente componente React (simplificado).

class SalesView extends Component<{}, State> { state: State = { salesData: null }; componentDidMount() { this.fetchSalesData(); } render() { if (this.state.salesData) { return <SalesChart salesData={this.state.salesData} />; } else { return <p>Loading</p>; } } async fetchSalesData() { let data = await new SalesService().fetchSalesData(); this.setState({ salesData: data }); } }

Durante el montaje, obtengo datos de una API, que he abstraído en una clase llamada SalesService . Esta clase quiero fetchSalesData , y para el método fetchSalesData quiero especificar los datos de retorno (en una promesa).

Esto es más o menos como quiero que se vea mi caso de prueba:

  • predefinir datos de prueba
  • importar SalesView
  • servicio de ventas simulado
  • configure mockSalesService para devolver una promesa que devuelva los datos de prueba predefinidos cuando se resuelva

  • crear el componente

  • esperar
  • ver instantánea

Probar el aspecto de SalesChart no es parte de esta pregunta, espero resolverlo utilizando Enzyme. He estado intentando docenas de cosas para burlarme de esta llamada asíncrona, pero parece que no puedo burlarme de esto correctamente. He encontrado los siguientes ejemplos de burla de Jest en línea, pero no parecen cubrir este uso básico.

Mis preguntas son:

  • ¿Cómo debería ser la clase de simulacros?
  • ¿Dónde debo colocar esta clase de simulacros?
  • ¿Cómo debo importar esta clase simulada?
  • ¿Cómo puedo saber que esta clase simulada reemplaza a la clase real?
  • ¿Cómo configurar la implementación simulada de una función específica de la clase simulada?
  • ¿Cómo espero en el caso de prueba la promesa de ser resuelta?

Un ejemplo que tengo que no funciona es el siguiente. El corredor de prueba se bloquea con el error de throw err; y la última línea en el seguimiento de la pila está at process._tickCallback (internal/process/next_tick.js:188:7)

# __tests__/SalesView-test.js import React from ''react''; import SalesView from ''../SalesView''; jest.mock(''../SalesService''); const salesServiceMock = require(''../SalesService'').default; const weekTestData = []; test(''SalesView shows chart after SalesService returns data'', async () => { salesServiceMock.fetchSalesData.mockImplementation(() => { console.log(''Mock is called''); return new Promise((resolve) => { process.nextTick(() => resolve(weekTestData)); }); }); const wrapper = await shallow(<SalesView/>); expect(wrapper).toMatchSnapshot(); });


A veces, cuando una prueba es difícil de escribir, está tratando de decirnos que tenemos un problema de diseño.

Creo que un refactor pequeño podría hacer las cosas mucho más fáciles: hacer que SalesService un colaborador en lugar de un interno.

Con esto quiero decir, en lugar de llamar a un new SalesService() dentro de su componente, acepte el servicio de ventas como prop por el código de llamada. Si lo hace, entonces el código de llamada también puede ser su prueba, en cuyo caso todo lo que necesita hacer es burlarse del propio Servicio de SalesService y devolver lo que quiera (utilizando sinon o cualquier otra biblioteca de burlas, o incluso simplemente creando un rollo a mano). talón).


Podrías potencialmente abstraer la new palabra clave utilizando un método SalesService.create() , luego usar jest.spyOn (object, methodName) para simular la implementación.

import SalesService from ''../SalesService ''; test(''SalesView shows chart after SalesService returns data'', async () => { const mockSalesService = { fetchSalesData: jest.fn(() => { return new Promise((resolve) => { process.nextTick(() => resolve(weekTestData)); }); }) }; const spy = jest.spyOn(SalesService, ''create'').mockImplementation(() => mockSalesService); const wrapper = await shallow(<SalesView />); expect(wrapper).toMatchSnapshot(); expect(spy).toHaveBeenCalled(); expect(mockSalesService.fetchSalesData).toHaveBeenCalled(); spy.mockReset(); spy.mockRestore(); });


Una forma "fea" que he usado en el pasado es hacer una especie de inyección de dependencia para hombres pobres.

Se basa en el hecho de que es posible que no desee realmente crear una instancia de SalesService cada vez que lo necesite, sino que desea mantener una sola instancia por aplicación, que todos usan. En mi caso, SalesService requería una configuración inicial que no quería repetir cada vez. [1]

Entonces, lo que hice fue tener un archivo services.ts que se parece a esto:

/// In services.ts let salesService: SalesService|null = null; export function setSalesService(s: SalesService) { salesService = s; } export function getSalesService() { if(salesService == null) throw new Error(''Bad stuff''); return salesService; }

Luego, en el index.tsx mi aplicación o en algún lugar similar tendría:

/// In index.tsx // initialize stuff const salesService = new SalesService(/* initialization parameters */) services.setSalesService(salesService); // other initialization, including calls to React.render etc.

En los componentes, puede usar getSalesService para obtener una referencia a la única instancia de SalesService por aplicación.

Cuando llega el momento de la prueba, solo necesita realizar una configuración en su mocha (o lo que sea) before o before beforeEach controlador para llamar a setSalesService con un objeto simulado.

Ahora, idealmente, querría pasar SalesService como apoyo para su componente, ya que es una entrada para él, y al usar getSalesService usted está ocultando esta dependencia y posiblemente le está causando dolor en el camino. Pero si lo necesita en un componente muy anidado, o si está utilizando un enrutador o algo así, resulta bastante difícil pasarlo como un accesorio.

También puede salirse con la suya usando algo como el context , para mantener todo dentro de Reaccionar como si fuera.

La solución "ideal" para esto sería algo así como la inyección de dependencia, pero esa no es una opción con React AFAIK.

[1] También puede ayudar a proporcionar un único punto para serializar llamadas de servicio remoto, que pueden ser necesarias en algún momento.