Mobx: Examples of testing mobx stores with async actions

Created on 19 Aug 2016  ·  10Comments  ·  Source: mobxjs/mobx

Can you provide example (best practice) to write unit-tests for MobX store.
for example:

class Store {
@observable data = asMap({});

@action fetchData = () => {
  MyAPIPromise('fetch things async').then(response => data.set(response.id, response))
}

How should i unit test this store?
of course i mock my API bridge, however what should i use to check my 'data' hashmap.
thanks in advance

❔ question

Most helpful comment

@ochervak I use when a lot in my unit tests to run the assertions at the proper moment (currently writing a blog post with some examples), but basically:

store.fetchData()
when(() => data.length > 0, () => { /* do assertions, end the test */ })

All 10 comments

@ochervak, you mean, how to check if your 'data' is correct? You can use something like

const shallowEquals = require('shallow-equals')
// ...
const expectedData = { expectedKey: 'expectedValue' };
expect(shallowEquals(myStore.data.toJS(), expectedData)).toBe(true);

Other Map methods can be also handy.

@ochervak I use when a lot in my unit tests to run the assertions at the proper moment (currently writing a blog post with some examples), but basically:

store.fetchData()
when(() => data.length > 0, () => { /* do assertions, end the test */ })

@mweststrate many thanks! i think issue can be closed

Now my test looks like:

it('should set active to a returned value on resolve', function(done) {
            TCStore.fetchActive();
            when(
                () => TCStore.data.has('active'),
                () => {
                    expect(TCStore.data.get('active')).to.not.equal(expectedResolve);
                    done();
                }
            );
        });

But there is a problem:

[mobx] An uncaught exception occurred while calculating your computed value, autorun or transformer. Or inside the render() method of an observer based React component. These functions should never throw e
xceptions as MobX will not always be able to recover from them. Please fix the error reported after this message or enable 'Pause on (caught) exceptions' in your debugger to find the root cause. In: 'When@
169'. For more details see https://github.com/mobxjs/mobx/issues/462  

So i need to wrap my assertions to setTimeout(()=>(), 0) to escape from mobx try\catch scope.

Can i fix this test without this ?

@ochervak ah, I always use tape which doesn't throw on failed assertions :) In principle you can ignore the error, it's just a warning. I'll think about a proper solution for this!

I’m using Jest and to get errors into the console when using when, I had to do the following:

it('sets initial values on init', function(done) {
    let Store = require('../Store').default;
    let instance = new Store();
    when(
      () => instance.isLoaded,
      () => {
        // async failing expects are not picked up with Jest,
        // you have to try/catch and call done.fail(e);
        try {
          expect(
            instance.bounds
          ).toEqual(
            '-122.93701171874999,37.01132594307015,' +
            '13.507690429687498,52.56675151567434'
          );
          done();
        } catch (e) {
          done.fail(e);
        }
      }
    );
  });

hope someone finds this useful.

thanks @mweststrate for helping me out on twitter.

Actually in our own projects we use the following utility (see below). Not that this is not just useful for when (or mobx-util.whenWithTimeout, but for any asynchronous process.

So that you can do

when(
  () => expr
  catchErrors(done, () => {
    assertions
  }
)
/**
 * In async tests, JEST will die (in watch mode) if an exception is thrown from a callback. This utility will catch
 * the errors instead and report the test as failed in these case *
 *
 * @param {jest.DoneCallback} done
 * @param {T} callback
 * @returns {T}
 */
export function catchErrors<T extends Function>(done: jest.DoneCallback, callback: T): T {
    return function() {
        try {
            callback.apply(null, arguments)
        } catch (e) {
            done.fail(e)
        }
    } as any as T
}

cc: @cpojer is there a generic strategy for this in Jest? Didn't see anything in the docs

I noticed this too and filed https://github.com/facebook/jest/issues/2059. We should definitely fix this in Jest.

Thanks @cpojer! I added our typical workaround to the issue

Closing for inactivity.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

josvos picture josvos  ·  3Comments

hellectronic picture hellectronic  ·  3Comments

kirhim picture kirhim  ·  3Comments

cafreeman picture cafreeman  ·  4Comments

ansarizafar picture ansarizafar  ·  4Comments