javascript - example - jest render
Pruebe una funciĆ³n React Component con Jest (2)
Original
En primer lugar, estoy siguiendo la arquitectura Flux .
Tengo un indicador que muestra una cantidad de segundos, por ejemplo: 30 segundos. Cada segundo muestra 1 segundo menos, entonces 29, 28, 27 hasta 0. Cuando llega a 0, borro el intervalo para que deje de repetirse. Además, disparo una acción. Cuando se envía esta acción, mi tienda me lo notifica. Entonces, cuando esto sucede, reinicio el intervalo a 30s y así sucesivamente. El componente se ve así:
var Indicator = React.createClass({
mixins: [SetIntervalMixin],
getInitialState: function(){
return{
elapsed: this.props.rate
};
},
getDefaultProps: function() {
return {
rate: 30
};
},
propTypes: {
rate: React.PropTypes.number.isRequired
},
componentDidMount: function() {
MyStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
MyStore.removeChangeListener(this._onChange);
},
refresh: function(){
this.setState({elapsed: this.state.elapsed-1})
if(this.state.elapsed == 0){
this.clearInterval();
TriggerAnAction();
}
},
render: function() {
return (
<p>{this.state.elapsed}s</p>
);
},
/**
* Event handler for ''change'' events coming from MyStore
*/
_onChange: function() {
this.setState({elapsed: this.props.rate}
this.setInterval(this.refresh, 1000);
}
});
module.exports = Indicator;
El componente funciona como se esperaba. Ahora, quiero probarlo con Jest. Sé que puedo usar renderIntoDocument, luego puedo establecer Time of Time 30s y verificar si mi component.state.elapsed es igual a 0 (por ejemplo).
Pero, lo que quiero probar aquí son cosas diferentes. Quiero probar si se llama a la función de actualización . Además, me gustaría probar que cuando mi estado transcurrido es 0, desencadena mi TriggerAnAction () . Ok, por lo primero que intenté hacer:
jest.dontMock(''../Indicator'');
describe(''Indicator'', function() {
it(''waits 1 second foreach tick'', function() {
var React = require(''react/addons'');
var Indicator = require(''../Indicator.js'');
var TestUtils = React.addons.TestUtils;
var Indicator = TestUtils.renderIntoDocument(
<Indicator />
);
expect(Indicator.refresh).toBeCalled();
});
});
Pero recibo el siguiente error al escribir la prueba npm:
Throws: Error: toBeCalled() should be used on a mock function
Vi de ReactTestUtils una función de mockComponent pero, dada su explicación, no estoy seguro de si es lo que necesito.
Ok, en este punto, estoy atascado. ¿Alguien puede darme alguna luz sobre cómo probar esas dos cosas que mencioné anteriormente?
Actualización 1, basada en la respuesta de Ian
Esa es la prueba que intento ejecutar (ver comentarios en algunas líneas):
jest.dontMock(''../Indicator'');
describe(''Indicator'', function() {
it(''waits 1 second foreach tick'', function() {
var React = require(''react/addons'');
var Indicator = require(''../Indicator.js'');
var TestUtils = React.addons.TestUtils;
var refresh = jest.genMockFunction();
Indicator.refresh = refresh;
var onChange = jest.genMockFunction();
Indicator._onChange = onChange;
onChange(); //Is that the way to call it?
expect(refresh).toBeCalled(); //Fails
expect(setInterval.mock.calls.length).toBe(1); //Fails
// I am trying to execute the 1 second timer till finishes (would be 60 seconds)
jest.runAllTimers();
expect(Indicator.state.elapsed).toBe(0); //Fails (I know is wrong but this is the idea)
expect(clearInterval.mock.calls.length).toBe(1); //Fails (should call this function when time elapsed is 0)
});
});
Todavía estoy entendiendo mal algo ...
Creo que entiendo lo que estás preguntando, ¡al menos una parte!
Comenzando con el error, la razón por la que está viendo eso es porque ha ordenado que no se burlen del módulo Indicador para que todos los internos sean como los ha escrito. Si desea probar que se llama a esa función en particular, le sugiero que cree una función simulada y la use en su lugar ...
var React = require(''react/addons'');
var Indicator = require(''../Indicator.js'');
var TestUtils = React.addons.TestUtils;
var refresh = jest.genMockFunction();
Indicator.refresh = refresh; // this gives you a mock function to query
Lo siguiente a tener en cuenta es que en realidad estás reasignando la variable Indicador en tu código de ejemplo, por lo que para el comportamiento correcto cambiaría el nombre de la segunda variable (como a continuación)
var indicatorComp = TestUtils.renderIntoDocument(<Indicator />);
Finalmente, si desea probar algo que cambia con el tiempo, use las funciones de TestUtils sobre la manipulación del temporizador ( http://facebook.github.io/jest/docs/timer-mocks.html ). En tu caso, creo que puedes hacer:
jest.runAllTimers();
expect(refresh).toBeCalled();
Alternativamente, y quizás un poco menos quisquilloso es confiar en las implementaciones simuladas de setTimeout y setInterval para razonar sobre su componente:
expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);
Otra cosa, para que funcione cualquiera de los cambios anteriores, creo que tendrás que activar manualmente el método onChange ya que tu componente estará trabajando inicialmente con una versión de tu tienda que se burló para que no ocurran eventos de cambio. También deberá asegurarse de haber configurado la broma para ignorar los módulos de reacción, de lo contrario también se burlarán automáticamente.
Prueba completa propuesta
jest.dontMock(''../Indicator'');
describe(''Indicator'', function() {
it(''waits 1 second for each tick'', function() {
var React = require(''react/addons'');
var TestUtils = React.addons.TestUtils;
var Indicator = require(''../Indicator.js'');
var refresh = jest.genMockFunction();
Indicator.refresh = refresh;
// trigger the store change event somehow
expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);
});
});
Parece que estás en el camino correcto. Solo para asegurarnos de que todos estén en la misma página para esta respuesta, eliminemos la terminología.
Mock : una función con comportamiento controlado por la prueba unitaria. Por lo general, se intercambian las funciones reales de algún objeto con una función simulada para garantizar que la función simulada se invoque correctamente. Jest proporciona simulaciones para cada función en un módulo automáticamente a menos que llame a jest.dontMock
en el nombre de ese módulo.
Clase de componente : esto es lo que devuelve React.createClass
. Lo usas para crear instancias de componentes (es más complicado que eso, pero esto es suficiente para nuestros propósitos).
Instancia de componente : una instancia representada real de una clase de componente. Esto es lo que obtendrías después de llamar a TestUtils.renderIntoDocument
o a muchas de las otras funciones de TestUtils
.
En su ejemplo actualizado de su pregunta, está generando simulaciones y adjuntándolas a la clase de componente en lugar de una instancia del componente. Además, solo desea simular las funciones que desea supervisar o cambiar; por ejemplo, se burla de _onChange
, pero en realidad no desea hacerlo, porque quiere que se comporte con normalidad; solo se trata de una refresh
que desea simular.
Aquí hay un conjunto propuesto de pruebas que escribí para este componente; los comentarios están en línea, así que publique un comentario si tiene alguna pregunta. La fuente de trabajo completa para este ejemplo y conjunto de pruebas está en https://github.com/BinaryMuse/so-jest-react-mock-example/tree/master ; deberías poder clonarlo y ejecutarlo sin problemas. Tenga en cuenta que tuve que hacer algunas suposiciones menores y cambios en el componente, ya que no todos los módulos referenciados estaban en su pregunta original.
/** @jsx React.DOM */
jest.dontMock(''../indicator'');
// any other modules `../indicator` uses that shouldn''t
// be mocked should also be passed to `jest.dontMock`
var React, IndicatorComponent, Indicator, TestUtils;
describe(''Indicator'', function() {
beforeEach(function() {
React = require(''react/addons'');
TestUtils = React.addons.TestUtils;
// Notice this is the Indicator *class*...
IndicatorComponent = require(''../indicator.js'');
// ...and this is an Indicator *instance* (rendered into the DOM).
Indicator = TestUtils.renderIntoDocument(<IndicatorComponent />);
// Jest will mock the functions on this module automatically for us.
TriggerAnAction = require(''../action'');
});
it(''waits 1 second foreach tick'', function() {
// Replace the `refresh` method on our component instance
// with a mock that we can use to make sure it was called.
// The mock function will not actually do anything by default.
Indicator.refresh = jest.genMockFunction();
// Manually call the real `_onChange`, which is supposed to set some
// state and start the interval for `refresh` on a 1000ms interval.
Indicator._onChange();
expect(Indicator.state.elapsed).toBe(30);
expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);
// Now we make sure `refresh` hasn''t been called yet.
expect(Indicator.refresh).not.toBeCalled();
// However, we do expect it to be called on the next interval tick.
jest.runOnlyPendingTimers();
expect(Indicator.refresh).toBeCalled();
});
it(''decrements elapsed by one each time refresh is called'', function() {
// We''ve already determined that `refresh` gets called correctly; now
// let''s make sure it does the right thing.
Indicator._onChange();
expect(Indicator.state.elapsed).toBe(30);
Indicator.refresh();
expect(Indicator.state.elapsed).toBe(29);
Indicator.refresh();
expect(Indicator.state.elapsed).toBe(28);
});
it(''calls TriggerAnAction when elapsed reaches zero'', function() {
Indicator.setState({elapsed: 1});
Indicator.refresh();
// We can use `toBeCalled` here because Jest automatically mocks any
// modules you don''t call `dontMock` on.
expect(TriggerAnAction).toBeCalled();
});
});