State Change
Testing for a state change is easy, and you can just run your action like a function.
Copy import { effects } from 'ferp' ;
const IncrementCounter = state => [
{ ... state , counter : state .counter + 1 } ,
effects .none () ,
];
describe ( 'IncremenetCounter' , () => {
it ( 'increments the counter state variable' , () => {
const initialState = { counter : 0 };
const [ state , _effect ] = IncrementCounter (initialState);
expect (state) .toDeepEqual ({
counter : 1 ,
});
});
});
Testing action builders is very similar, too:
Copy import { effects } from 'ferp' ;
const IncrementCounterByN = n => state => [
{ ... state , counter : state .counter + n } ,
effects .none () ,
];
describe ( 'IncremenetCounterByN' , () => {
it ( 'increments the counter state variable using the provided value' , () => {
const initialState = { counter : 0 };
const [ state , _effect ] = IncrementCounterByN ( 999 )(initialState);
expect (state) .toDeepEqual ({
counter : 999 ,
});
});
});
Result of Side-Effects
Testing that the correct side effects can be a little more tricky. The problem is that effects touch the outside world that is not controlled by Ferp. Running these end-to-end just like the effect testing is likely your best bet.
actions.spec.js actions.js todoFx.js
Copy import * as sinon from 'sinon' ;
import { app } from 'ferp' ;
import * as actions from './actions.js' ;
describe ( 'RequestTodo' , () => {
it ( 'does not modify the state' , () => {
const initialState = { todos : [] , externals : { fetch : window .fetch } };
const [ state ] = actions .RequestTodo ( 1 )(initialState);
expect (state) .toBe (initialState);
});
it ( 'successfully fetches a todo' , (done) => {
const expectedTodo = { id : 1 , text : 'foo' , completed : false };
const fakeFetch = sinon .fake ((arg) => {
return {
json : Promise .resolve (expectedTodo)
};
});
const initialState = { todos : [] , externals : { fetch : fakeFetch } };
const expectedTodosInOrder = [
[] , // init
[] , // request
[expectedTodo] , // on success
];
app ({
init : actions .RequestTodo ( expectedTodo .id)(initialState) ,
observe : ([state , effect]) => {
expect ( state .todos)
.toDeepEqual ( expectedTodosInOrder .unshift ());
if ( expectedTodosInOrder . length === 0 ) {
done ();
}
} ,
});
});
});
Copy import { effects } from 'ferp' ;
import * as todoFx from './todoFx.js' ;
export const AddTodo = (todo) => (state) => [
{ ... state , todos : state . todos .concat (todo) } ,
effects .none () ,
];
export const RequestTodo = (todoId) => (state) => [
state ,
todoApi .getTodo ( state . external .fetch , todoId , AddTodo) ,
];
Copy import { effects } from 'ferp' ;
export const getTodo = (fetchFn , id , onSuccess , onFailure) => effects .defer (
fetchFn ( `https://jsonplaceholder.typicode.com/todos/ ${ id } ` )
.then (response => response .json ())
.then (data => effects .act ( onSuccess (data)))
.catch ((err) => (
onFailure
? effects .act ( onFailure (err))
: effects .none ()
))
);
Yes, that test code looks clunky, so why not make a helper to make things a little easier?
Copy import { app } from 'ferp' ;
export const endToEndTest = (init , assertions , done) => app ({
init ,
observe : ( ... args) => {
const assertion = assertions .unshift ();
assertion ( ... args);
if ( assertions . length === 0 ) {
done ();
}
} ,
});
Which would result in:
actions.spec.js
Copy import { endToEndTest } from './support/endToEndTest.js' ;
// ...
it ( 'successfully fetches a todo' , (done) => {
const expectedTodo = { id : 1 , text : 'foo' , completed : false };
const fakeFetch = sinon .fake ((arg) => {
return {
json : Promise .resolve (expectedTodo)
};
});
const initialState = { todos : [] , externals : { fetch : fakeFetch } };
endToEndTest (
actions .RequestTodo ( expectedTodo .id)(initialState) ,
[
([state]) => expect ( state .todos) .toDeepEqual ([]) , // init
([state]) => expect ( state .todos) .toDeepEqual ([]) , // request
([state]) => expect ( state .todos) .toDeepEqual ([expectedTodo]) , // on success
] ,
done
);
});
There may be other forms of helpers that can make the end-to-end portions of your tests to be more readable and manageable.