Here’s a framework I have come up with to test this flow. This assumes you’re using the vanilla jasmine/karma setup from angular-cli as a test harness environment. First, our mock network service. In ordinary operation, we want the network requests to just respond immediately. However, when we’re testing the loading controller flow, we need to be able to interrupt this. From a client (test) perspective, when we call freeze on our service, all subscriptions will block until we call unfreeze.
I find the whole freezing thing confusing I have to admit. Couldn’t you grab the backend and loadings variables up front, then call refresh. With refresh called you can check if loading was created/presented. Then call tick() which should advance to when the async call resolved. You should then be able to check the dismiss was called, and that you have the value from the service. There’s probably a reason that won’t work, but just by me looking at the code I don’t know what that is.
Would that still be able to catch bugs like double-dismisses? I’m still paranoid about that sort of thing from my years of banging against memory allocation bugs in C++.
It is definitely confusing, and perhaps this isn’t the best case to explain why I think it’s needed. Typically mock services simply return Observables that fire immediately. I often have components that fire network requests in their constructors. Sometimes I have tests that don’t care when the fake network request resolves, and sometimes I have tests that want to checkpoint the point whereby the asynchronous request has been fired but not yet resolved. I struggled to come up with a way to serve both needs, and this is the best I’ve got to date.
Perhaps I’m being paranoid, but this doesn’t seem to be sufficient. I want to know that the loader was presented while the network request was outstanding, and dismissed once it resolved. These two expectations don’t seem to tell me what the situation was in between them, and that’s what I’m really trying to test.
As far as timing, I would think it would be the same:
page.refresh();
expect(loadingSpy.create.calls.count()).toEqual(1); // create called only once because it's called immediately
expect(loadingSpy.dismiss).not.toHaveBeenCalled(); // dismiss hasn't been called yet because async hasn't finished
tick(); // This tick means the async call has resolved
expect(loadingSpy.dismiss.calls.count()).toEqual(1); // dismiss called once because async is finished
I feel like that covers both the multiple calls and the timing with far less code. These will actually fail the tests if dismiss is double called, where it seems like your mock object will just write to the console? It also keeps the logic of what you want to test (if dismiss get’s called too much) in the test, instead of in the mock object, which isn’t as good of a place for it.
This is a great point that I am struggling with. How much logic do I put in my mocks which can be shared by all tests that use them, and how much do I put in the tests themselves?
Ya I wouldn’t put logic into a mock that the object it’s mocking doesn’t have, that’s going down a dangerous path. If double dismissal is a real life concern, you could consider writing a wrapper service for the loader that logs the error to the console on a double dismissal for the real service. Then your mock object is a bit more legitimate, however it still won’t fail the test if it’s called multiple times.
Just an additional thought to this, if you have that pattern of showing and hiding a loading spinner with your requests, you could extend the Http service with a new one that handles the showing and hiding of the spinner. That would encapsulate that logic, and you could have just one unit test for your new service.