mobx-state-tree icon indicating copy to clipboard operation
mobx-state-tree copied to clipboard

Testing flow functions in Jest

Open Claudiaapalagie opened this issue 3 years ago • 1 comments

Question

  • [ x] I've checked documentation and searched for existing issues
  • [ x] I tried GitHub Discussions

Can someone please help me ? :( I have some nested Promises, basically, on the success of the first one, I am making another request and so on. Unfortunately, when I want to test that, code coverage reports is showing me that I am only reaching the first call but then it's getting stuck in .then() ...it's not entering there, the success callback is not made. I read that this is because of "yield" which doesn't behave the same in test mode and it's not waiting for the response to come.

In the store, all my fetching functions are defined as follows:

 .actions((self) => ({
    fetchSomething: flow(function* fetchSomething(param1: string, param2: string) {
      try {
        self.isLoading = true;
        const header: Header = getParent<typeof RootStore>(self).getHeader(param2);
        const someFetch = (yield axios
          .get(`${config.apiEndpoint.endpoint}/${param1}`, {
            headers: header,
          })
          .then((res: AxiosResponse<APIResponse<SnapshotIn<typeof SomeModel>>>) => {
            return res.data;
          })) as APIResponseSomething['results'][number];
        self.instanceofSometing = Modela.create(someFetch);
      } catch (err) {
        // Temporarily commented out because it crashes the development environment 
        // throw new Error(err);
      } finally {
        self.isLoading = false;
      }
    }),

Please, how would you make this work in tests? Using Jest and Enzyme/RTL

Claudiaapalagie avatar Nov 23 '21 07:11 Claudiaapalagie

You should be able to remove the then requirement as the yield returns the resolved value. The syntax is very similar to async/await

 const someFetch = yield axios.get(`${config.apiEndpoint.endpoint}/${param1}`, { headers: header })
 self.instanceofSometing = Modela.create(someFetch.data);

In testing you could jest.mock('axios') then mock the resolvedValue() of axios something like:

const mockAxios = {
 get: jest.fn()
};

jest.mock('axios', ()=>mockAxios)
...

it('does the thing', async ()=>{
  mockAxios.get.mockResolveValue({ data: 'foobar' })
  await store.fetchSomething('one', 'two');
  expect(store.instanceOfSometing.data).toEqual('foobar');
```

Hopefully that pseudocode should point you in the right direction.



clgeoio avatar Nov 30 '21 21:11 clgeoio

My solution – remove the flow wrapper with jest.spyOn:

import axios, { AxiosResponse } from 'axios'
import * as mobx from 'mobx'

export const mockMobxFlowGenerator = () => {
  const s1 = jest.spyOn(mobx, 'flow').mockImplementation(arg => arg)
  const s2 = jest
    .spyOn(mobx, 'makeObservable')
    .mockImplementation(() => jest.fn())

  beforeEach(() => {
    s1.mockRestore()
    s2.mockRestore()
  })
}

export type THttpClient = Generator<unknown, void, AxiosResponse>

And now use it before creating a store:

it('Checks login method', () => {
    mockMobxFlowGenerator()
    const loginStore = new LoginStore()
    const login = loginStore.login('[email protected]', 'password') as unknown as THttpClient
    
    login.next()
    expect(loginStore.errorType).toEqual('')
})

thanksyouall avatar Nov 23 '22 15:11 thanksyouall

Hey @Claudiaapalagie - looks like we got some answers here. I am going to convert this from an issue to a discussion, and then I will invite you to mark one of the answers as the correct one, or feel free to ask for additional help.

coolsoftwaretyler avatar Jun 27 '23 04:06 coolsoftwaretyler