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.
- Hackernoon : No utiliza llamadas asíncronas.
- Blog de tecnología de Wehkamp : no utiliza llamadas asíncronas
- Agatha Krzywda : no usa llamadas asíncronas
- GitConnected : no usa una clase con una función para simularse
- Tutorial de Jest Un ejemplo asíncrono : no usa una clase con una función para simularse
- Tutorial de Jest Prueba del código asíncrono : no utiliza una clase con una función para simularse
- SO pregunta 43749845 : No puedo conectar el simulacro a la implementación real de esta manera
- 42638889 : está usando la inyección de dependencia, no estoy
- 46718663 : no muestra cómo se implementa la clase simulada real
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.