Thanks for this project, I really like the way how state management is handled.
Pure redux actions are easy to test, and even async actions handled by redux-saga or redux-observables offer a way of testing the code in isolation.
I was looking for tests in the example projects using mobx-state-tree, but I could not find a test testing substeps within a generator function.
Take the following code as an example (taken from https://github.com/mobxjs/mobx-state-tree/blob/master/docs/async-actions.md#using-generators):
fetchProjects: flow(function* fetchProjects() { // <- note the star, this a generator function!
self.githubProjects = []
self.state = "pending"
try {
// ... yield can be used in async/await style
self.githubProjects = yield fetchGithubProjectsSomehow()
self.state = "done"
} catch (e) {
// ... including try/catch error handling
console.error("Failed to fetch projects", error)
self.state = "error"
}
})
My testing approach would be the following:
describe('Test', () => {
it('should do something', () => {
// .generator does not exist on Promise
const generator = model.fetchProjects().generator;
expect(model.githubProjects).toEqual([]);
expect(model.state).toBe('pending');
generator.next();
expect(model.state).toBe('done');
});
});
Right now the return value of fetchProjects is a Promise, so the test would fail obviously. Did I miss something, or is it currently not possible to test an action that way?
Probably useful resources: https://redux-saga.js.org/docs/advanced/Testing.html and https://redux-observable.js.org/docs/recipes/WritingTests.html
No, but it is a super cool idea. I think it should be possible to expose the generator together with the promise by mixing it in.
Alternatively you could think about exposing the generator without it being wrapped in flow, for testing purposes. for example:
const x = types.model().actions(self=> {
function* fetchProjectsImpl() {
}
return {
fetchProjects: flow(fetchProjectsImpl),
_fetchProjectGenerator() { return fetchProjectsImpl }
})
)
Is this the only recommended approach for testing async actions in MST @mweststrate ?
@nbirrell-bom Here is how I am currently testing my async/flow functions (using Jest):
The test:
it('should update the results on success', async () => {
// in this particular case I pass a promise, but in other places I have mocks
// that return the promise in the flow
const service = Promise.resolve(results);
model.asyncFunction(service);
await service;
expect(model.total).toBe(24);
});
The flow:
asyncFunction: flow(function* aFunction(service) {
self.loading = true;
try {
const data = yield service;
self.parseData(data);
} catch (error) {
logger.error(error)
} finally {
self.loading = false;
}
})
What about adding the "Expose Generator" idea to the TODO list?
@jamespolanco does simpy await model.asyncFunction(service) suffice in your case?
@luisherranz fine with that
@luisherranz fine with that
Ok, I'll open a new issue.
@mweststrate In this case, it does work. I have had issues in the past testing nested promises with Async/Await where you end up with await await methodToCall() scenarios and out of a knee-jerk reaction I went with the await on the passed promise vs. on the method return promise
Cool! closing
Most helpful comment
@nbirrell-bom Here is how I am currently testing my async/flow functions (using Jest):
The test:
The flow: