ferp-js
  • What is Ferp
  • Getting Started
    • Installing Ferp in Your Application
    • What Does Ferp Provide
  • Building Your First App
    • The Boilerplate
  • Understanding Effects in Ferp
    • What is a Ferp Effect?
    • Core Effects
    • Composing Custom Effects
    • Testing Your Effects
  • Understanding Actions in Ferp
    • What is a Ferp Action?
    • Writing Your Own Actions
    • Testing Actions
  • Understanding Subscriptions in Ferp
    • What is a Ferp Subscription?
    • Composing Custom Subscriptions
    • Testing Your Subscriptions
  • Advanced Usage
    • Third-Party Libraries and Objects
Powered by GitBook
On this page
  • State Change
  • Result of Side-Effects

Was this helpful?

  1. Understanding Actions in Ferp

Testing Actions

State Change

Testing for a state change is easy, and you can just run your action like a function.

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:

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.

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();
        }
      },
    });
  });
});
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),
];
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?

support/endToEndTest.js
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:

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.

PreviousWriting Your Own ActionsNextWhat is a Ferp Subscription?

Last updated 3 years ago

Was this helpful?