I have a Vue button click handler that based on the arguments it takes, can:
- call just request A
- call just request B
- call request A and B - but it should call them one after the other (if request A returns successfully, then call request B. Basically, the implementation can't use
Promise.all()).
My problem is that I don't know how to unit test the "call A and B one after the other" behavior with Jest.
Implementation
Here's the event handler, it runs after clicking a button:
const loadA = this.$store.dispatch['loadA']; //note these are FUNCTIONS THAT RETURNS A PROMISE
const loadB = this.$store.dispatch['loadB'];
async makeRequests(shouldMakeRequestA, shouldMakeRequestB) {
const requests = [];
if(shouldMakeRequestA) requests.push(loadA);
if(shouldMakeRequestB) requests.push(loadB);
for(const request in requests) {
await request(); //waits for request to return before calling for second one
}
}
Testing
The correct test case should:
FAIL❌ when:
the implementation calls two requests at the same time eg.:
() => { Promise.all([loadA(), loadB()]) }() => { loadA(); loadB() }
PASS✔️ when:
- the implementation calls loadA, waits for it's promise to resolve, then calls loadB, eg:
() => {await loadA(); await loadB();}
- the implementation calls loadA, waits for it's promise to resolve, then calls loadB, eg:
Here's my take on the test case I described, but it seems very fragile to race conditions and would be difficult to understand to colleagues I assume.
//component.spec.js
import MyCustomButton from '@/components/MyCustomButton.vue'
import TheComponentWeAreTesting from '@/components/TheComponentWeAreTesting'
describe('foo', () => {
const resolveAfterOneSecond = () => new Promise(r => setTimeout(r, 1000));
let wrapper;
const loadA = jest.fn(resolveAfterOneSecond);
const loadB = jest.fn(resolveAfterOneSecond);
beforeEach(() => {
wrapper = shallowMount(TheComponentWeAreTesting, store: new Vuex.Store({actions: {loadA, loadB}});
})
it('runs A and B one after the other', async () => {
wrapper.find(MyCustomButton).vm.$emit('click');
/*
One of the major problems with my approach is that
I don't know how much time has passed after I await $nextTick.
Both requests resolve after 2000 ms total (as mocked above with setTimeout)
But how much time has passed after $nextTick is resolved?
700ms? 1300? 1999ms?
*/
await wrapper.vm.$nextTick();
/*
Because I don't know how much time did it take for $nextTick to resolve
I need to wait a few extra ms so the test passes at all
Basically, you have to take my word for it that "500ms" is the value that makes the test pass
*/
awat new Promise(r => setTimeout(r, 500));
const callCount = loadA.mock.calls.length + loadB.mock.calls.length;
expect(callCount).toBe(1); //expect first request to have been sent out, but the second one shouldn't be sent out yet at this point
}
}
Is there a better way to test this behavior? I know of eg jest.advanceTimersByTime, but that advances ALL timers, not the current one.