javascriptreactjsxstate

Testing actions reliably


I'm using xstate to implement a login flow. I have a machine where the initialState invokes a Promise, and if it's rejected it will redirect to a state that has an entry action. I would like to test that the action is called at the right time properly.

machine.ts

{
  id: 'entrance',
  initial: States.FETCHING_SESSION,
  states: {
    [States.FETCHING_SESSION]: {
      invoke: {
        src: 'fetchSession',
        onError: {
          target: States.LOGGED_OUT,
        }
      }
    },
    [States.LOGGED_OUT]: {
      entry: ['navigateToLogin']
      type: 'final'
    },
  }
}

machine.spec.ts

    const mockFetchSession = jest.fn()
      .mockRejectedValueOnce({ error: new Error('401 unauthorized') })
    const mockNavigateToLogin = jest.fn()

    const service = interpret(entranceMachine.withConfig({
      services: {
        fetchSession: mockFetchSession
      },
      actions: {
        navigateToLogin: mockNavigateToLogin
      }
    }))

    it('Goes to login page on fail', (done) => {
      service.onTransition((state) => {
        expect(state.matches(States.LOGGED_OUT))
        expect(mockNavigateToLogin).toHaveBeenCalled() // <- this test case always fails
        done()
      })
      service.start()
    })

I managed to make it work in kinda of an ugly way by wrapping expect around a setTimout.

setTimeout(() => expect(mockNavigateToLogin).toHaveBeenCalled(), 100)

I wonder if there is a better option? Thanks


Solution

  • First, I suspect setTimeout just skips the expect..

    I ran into the same issue using withConfig. The actions at least, are not called at all even if they appear in machine.options.actions.

    My code looks like this:

    const myAction = jest.fn();
    const actions = { myAction };
    const withCfg = machine.withConfig({
      actions,
    });
    const spy = jest
      .spyOn(actions, 'myAction')
      .mockImplementation(() => myAction());
    // ...
    
    expect(spy).toHaveBeenCalledTimes(1);
    

    Note that in application those actions are executed just fine