Ok, ok. I know Angular 2 is still fairly new, but working with it has been… an experience. There’s so much information that is out of date or just plain wrong. And that is doubly true of testing. No matter how important every claims testing is to modern development, it always lags behind.
Recent case in point, I was out interviewing for new jobs and after a few places gave me a spiel about how their developers are geniuses (aren’t they always) and how their products are amazing (ditto), I asked what testing suite they used. They paused and said, ‘well we don’t have tests now, but we totally want to do it in the future.’ What?!.
Anyway, testing in Angular still has a long way to go in terms of documentation. I found myself in a situation where I needed to test the results of an api call. The api returned some rather weird information and it needed to be formatted and translated a bit before I wanted to push it into the app. Furthermore, I knew the endpoint was going to change soon which means that there would need to be some refactoring so tests are an absolute necessity.
Alright, so there’s a need. Seems like a pretty common need. I’ll just poke around the documentation a little and find what I need.
Turns out that’s not so easy. There is something call MockBackEnd but it’s marked ‘experimental’ in the documentation and the documentation is confusing at that. I found a few other blog posts but I couldn’t quite get them to work (more of a statement of my ability than that of the authors).
So after some experimentation, I give you my approach. It pulls some methods from the other documents, but I think it is a little more simple.
The project is organized into components. I’ll be pulling data from jsonplaceholder. Specifically, I’ll be pulling users data.
Our component will look like this:
/Users users.component.ts users.component.spec.ts users.service.ts users.service.spec.ts users.http.service.ts
It’s a pretty standard set up. The only difference is that I moving all the api calls to a separate http service. The reason here is
- they are in one clear place if an api endpoint changes
- they are a little easier to mock (if I wanted to go that route)
- Separation of concerns and all that stuff
Ok. Let’s get rolling.
Here’s a look at the code I want to test. I’ll do the service in this post and a component in a separate post.
First is the http service. This is where only the most basic REST calls are made.
This is the service. It will call the http service and do any other data manipulations that I need.
Finally, here is the initial test. This one is generated by Angular CLI. Essentially, it is injecting the service into the test suite and then later into each assertion. This mirrors how I would inject it into a component.
Now the question is how do I test the response of the api call? The first step is creating a stable set of test data.
Creating mock data is important for a few reasons. First, I need to decouple the test from an actual api call. That would be slow and expensive. So I need something that can return data as if it were an api. Thus, I need data.
Second, I need to make sure that the same data is return every time. It’s hard to write tests against a live api because the data is bound to change and will break tests. Having mock data avoids this issue. The problem, of course, is that if the api changes I will not have the benefit of failing tests, but that is a different issue.
I try to keep all component related material together.
I also try to keep tests clean if I can.
As a result, I tend to create a separate file just of mock data.
In this case, I’ll create
Well, really it’s more like the fourth or fifth test, but I’ll ignore all the other ones created by angular cli.
Our first test will be pretty simple. I just need to make sure that getUsers returns the mockdata. There are a couple of tricks, though. Since the http client returns an observable I need to be sure to subscribe to the data before I can make an assertion.
Here’s how a basic test will look.
It will, of course, fail. But it will not fail for good TDD reasons.
Instead you’ll get some ugly message that says something like this:
Error: No provider for Http!
It’s not failing because the assertions do not match. It’s failing because not all the dependencies are injected.
At this point, I can keep including insertions, but it’s probably a good time to think about mocks.
Mock Option 1: Mock a Service
Before I get too far, I know. Mocks are bad. Mocks are code smells and so on. However, there are some good reasons to mock things and a service that would contact something outside the application is a pretty good reason.
In that spirit, let’s mock the
users.http.service. There’s only one get method, so the mock will be very short.
This seems easy enough. I create an observable and pass that along.
Unfortunately, that will not work. The angular http client is doing a lot more than returning an observable.
It is also returning a
object which is itself returning a
which contains stringified data.
So the final mock file will look like this:
That’s not too bad, but it’s clear that the file will grow and grow. And, of course, that’s just another file mucking up the place.
Fortunately, there is a way around this and that is the built in MockBackend class.
Mock Option 2: MockBackend
MockBackend is essentially a built in class to handle all that mocking for us. It also prevents us from needing to make a lot of extra files.
Here’s the script:
Notice the changes.
I’ve included many of the things from the previous mock in our test. There include:
ResponseOptions, and the
I’ve also included the
MockBackend to capture the http requests and generate an observable and
MockConnnection to capture the request and
route it pass it the response I want.
In the providers, instead of overriding
UsersHttpService, I am overriding
Finally, in the actual test, I inject MockBackend.
Then, I build the
Response exactly how I did in the previous example and use
MockConnection to capture the request.
In a funny way, these built in options create more clutter.
At the very least, it require more typing.
The connection can be moved to a
beforeEach and I can reuse it over and over.
However, I think the biggest advantage is that it keeps the testing overhead in the test file.
It prevents us from needing to create a lot of separate files.