
Angular - Mocking the API
Angular - Mocking the API
You may think your application is working, but if the unit testing doesn’t pass, you’ve got nothing. Turn your red lights green by mocking the HTTP Client.
Source code available at: https://github.com/JasperKent/mocking…
Server available at: https://github.com/JasperKent/Restful…
Content
6.72 -> Last time then we introduced this idea of full
stack development. So we had a back-end server
11.6 -> written in C#/.NET connecting to an SQLite
database and presenting an API which then our
17.76 -> front end - written in Angular - could get hold
of the data from and display it and manipulate
22.32 -> it in whatever way we chose. And so we can see
all that working. There we've got the actual
27.12 -> Angular app. So we've got our list of all
the individual ratings - so each book can
33.12 -> have multiple ratings - and then we've got the
summary that just takes the average of the ratings
39.04 -> for each book. We also had this ability to add a
review, so I could give ‘Dr No’ yet another review
48.72 -> and we see that listed in there. And then
that's all working through the back end,
52.8 -> so we can just see that here with the in-process
server. So that's just showing us that's running,
57.44 -> no need to look at the details of that. So it's
all working in its very basic way, but there
62.64 -> was a fundamental problem that I completely hid
away and didn't mention, which is the fact that,
69.04 -> although the actual application is working,
the unit tests are now going to be failing
73.68 -> completely. So if I just bring up VSCode, you
can see there we've got the application running
78.48 -> - no problems - but if I go to a different window
and just type in ‘ng test’ to do the unit testing
87.36 -> then we can see we're getting four failures.
And if we just bring in the browser we can
92.64 -> see that the basic thing it's objecting to is
that it cannot inject this ‘httpClient’. So
98.88 -> that's happening again and again and again - that
HttpClient is missing. And that's really exactly
104.72 -> the sort of thing you'd expect, because remember
what we did last time in introducing this idea,
109.6 -> we had our BookReviewRepositoryService and
that requires to be injected this thing called
117.12 -> ‘HttpClient’ and that we configured for the
actual running system. But we didn't configure
122.4 -> that for the test system, and so not only does the
BookReviewRepositoryService test fail - because
130.56 -> when it tries to create the object it's not
got the thing that we need to inject - but
135.84 -> anything that depends on that also fails. So for
example the all-reviews-component-spec.ts, because
142.24 -> that depends in its constructor
on the BookReviewRepositoryService
147.2 -> and the BookReviewRepositoryService depends on,
as we just saw, HttpClient. That's why everything
152.64 -> starts to break down. And the only one that’s not
going to have a problem is going to actually be
156 -> our BookReviewSummaryService because although
that does require the BookReviewRepositoryService
162.88 -> to be injected, actually if we look at the
tests, that's the one where we were mocking
169.2 -> the BookReviewRepositoryService, so that's where
we create the mock and of course the mock doesn't
173.92 -> depend on HttpClient. So this bit works okay.
So what we need to do is mock the HttpClient.
180.88 -> One thing you'll also notice actually, we don't
really need to do very much with it yet because
184.32 -> all of these tests that are failing are really
simply failing on their first auto-generated test
191.6 -> ‘it (‘should create’)’ so we're not really testing
that they're interacting with the HttpClient
196.08 -> correctly. But in order to get them just to
be created we've got to have this mock idea.
200.64 -> But thankfully we don't have to do really very
much work for ourselves, unlike when we were
205.28 -> mocking one of our own classes. So as we saw
here where we had the summary service tests,
211.36 -> we had to use this ‘jasmine.createSpyObject’ and
all of that work that we looked at in the earlier
216.56 -> video. Because HttpClient is so widely used that
is actually predefined for us. So all we've got
223.44 -> to do is include this predefined idea, so we'll go
into each of these and all we need to do is in the
230.72 -> imports on the test we need to add an additional
import. So in addition to whatever else is there,
237.2 -> I’ve got to put in this ‘HttpClientTestingModule’.
Once again, like we saw with httpClient itself,
246.88 -> for some reason Visual Studio Code is not
good at importing this for you, so what we'll
252.32 -> have to do is just say ‘import’ and then the
name of the item and then 'from' and then it's
262.857 -> ‘@angular/common/http/testing’. And so now you can
see that's all happy and really for that one test
276.08 -> that's all we've got to do. So you can see down
the bottom we've got four tests failing at the
279.84 -> moment. If I just save that it'll build and run
the tests again and that's gone down to three. So
285.28 -> that much has worked in the one place we put it.
So let's now put it in the other ones. So we'll
290.64 -> just grab that and then we'll go to the next one,
which is the all-reviews-component.spec and hasn't
296.8 -> got any imports at the moment, so we'll just
pop that in. We don't need the FormsModule here
302.16 -> and we should see once you've managed to get
Visual Studio to understand where it is once, then
306.8 -> actually it starts doing things automatically.
So that's the one we want to put into there.
311.84 -> Then also we'll need this in the summary
component, and again get hold of the import,
319.12 -> and then lastly in the repository service tests
themselves, that's where we've got to put that in.
324.16 -> We haven't got anything at all in there, but in
there's where it goes; get hold of it. And now if
329.92 -> we save all of those and we can see that's working
because we've now configured it so that it injects
335.84 -> that HttpClientTestingModule into any situation
where an HttpClient is required. And so we're
342.72 -> effectively mocking it. So that's got the code
working to the extent that we passed the tests,
348.08 -> but we still haven't got any really sensible
tests. So let's actually do that. And I won't do
353.04 -> all of them - you should be thorough when you're
doing testing in a professional application,
357.12 -> but here we'll just do a couple of examples.
And so the first one we'll do, let's do it on
361.44 -> the AllReviewsComponent. So at the moment again
we've just got that ‘it (‘should create’)’ just to
368.4 -> basically make sure we can create the component.
But we want to make sure that this now correctly
374.72 -> gets hold of the list of all of the books, and
once again we want to do that with mock data.
381.12 -> And we've already done a bit of this, so we can
steal the mock data from somewhere else. So if we
386.08 -> go into the summary service spec – here remember,
we had already created some mock data - so let's
396.48 -> grab that and then put that same mock data into
our all reviews test code. So pop that in at the
405.68 -> top there, get hold of the BookReview import and
then what I’m going to do is add another test. So
415.28 -> typically easiest just to copy and paste
these and it should get the correct reviews,
426.32 -> okay? So the component should have that and then
remember these are stored on the component. So
431.52 -> what I can do is say ‘expect’ and then I’m going
to say ‘component.reviews’ so that's our list
439.52 -> of reviews that will eventually come through from
the mock HTTP. And the first thing I’m going to do
444.8 -> is just a ‘toHaveSize’ and then ‘3’ - so that's a
nice easy way just to check that we can see we've
453.2 -> got the three mock reviews here. So that'll test
for that. And then let's do individual tests, so
458.56 -> we're going to say ‘expect(component.reviews[0])’
and then we'll say ‘toEqual’, and then basically
471.52 -> it's just got to equal what we had there,
so let's just steal that ‘book-1’ rating 5,
478 -> pop that in there, tidy it up a little because
it doesn't really need to be on multiple lines,
484.56 -> and that's that one. And then we're going to be
thorough, so we'll test all three of those. So 0
491.04 -> and 1 and 2, and again if you look up here the
second one was ‘book-1’ with a rating of 1,
498.8 -> third one was ‘book-2’ with a rating of 4.
502.4 -> So that looks like it should be our
test, but if we save that one and run it,
507.6 -> then again we're getting a failure. If we look at
the browser we can see it's got an empty array;
514.16 -> it's expecting it to have size three. So three was
what we wanted, but we're not getting anything and
518.64 -> that's simply because we haven't actually hooked
this up to get those mock reviews in through
525.36 -> the mock HttpClient. So that's where we need to do
a little bit more work. So what we're going to do
530.72 -> now is here we're going to say ‘let’ and then
we'll call this ‘httpTestingController’ - you
540.8 -> can call it what you like, but that's the
normal name because that basically is the type
545.04 -> that it is - so ‘HttpTestingController’ there,
which we should be able to do an import of. So
551.68 -> that's coming in from the same place. So this
gives us the ability to configure and set up
557.52 -> our mock HttpClient. Having declared it we
then need to create it, and so we do that
565.12 -> just here after we've generated ‘component’. So
we're going to say that ‘=’ and then we can say
572.069 -> ‘testBed.inject’ and then we just have to give
it the type, which remember was that same thing
580 -> again, but with a capital ‘H’ because it's a type.
And so that has given us this object that we can
587.76 -> now configure. And then the last thing we have to
do is we have to tell it to expect a GET request
595.76 -> to the URL and then tell it what to
do when it gets back. So remember,
599.68 -> if we look at the actual repository we can see
that in the constructor - calls ‘loadReviews’
605.36 -> and that is where we make our request back to
the server, or back to the server if we've got
610.16 -> this configured for the real application; to our
mock HttpClient if not. And what we need to do
615.44 -> is check that it's gone to the right URL, that
it's a GET operation, rather than a POST - we'll look
621.6 -> at the POST later on - and then when it does that,
we've got to make sure that we just give back
627.12 -> as a result that mock data that we were just
looking at. So let's do that. So what we do at
633.76 -> the beginning of this test, before we actually do
all of that checking, we're going to say ‘const
639.76 -> request =’ and then on our
‘httpTestingController’.
645.52 -> We're going to say ‘expectOne’ - so we're going
to say you should get a single call - and then
653.28 -> we have to specify the precise rules of what this
request is going to be. So let's just do this with
661.76 -> an arrow function. So I’ll just call this ‘data’,
but then what we can do with that is we can see
667.84 -> the various bits of data we can check.
So if I say ‘data.url’ that's going to be
675.12 -> the URL that we're expecting should be passed
in, and we can obviously just steal that from
680.64 -> here. So if I just grab hold of that - bear
in mind, as I mentioned last time, not really
684.8 -> a good idea to have that hard coded because
‘localhost:5001’ is only during development,
690 -> but we'll look at that later, how we can get
configuration data in there. But we're going
693.68 -> to check that the URL is what we expect it to
be, and we're going to check that the method
701.68 -> is ‘GET’ because, remember, that's what we
said we wanted. So that will verify that that
708.64 -> request comes through, comes through only once,
matches that, and if everything's matched that
712.32 -> gives a green light - otherwise a red light.
The other thing we've got to do, remember,
716.08 -> is we've now got to return the mock data. So I now
say ‘request.flush’ and this is what will actually
725.36 -> kick off the whole process. So before that the
request won't actually be processed until you do
730.08 -> the ‘flush’. And then we can pass in there the
data that we want to send back, which was our
734.8 -> ‘mockReviews’, remember, that we already pasted
in from elsewhere. So doing all of that means
741.92 -> that when we do the flush it will send the data
back. That all happens in the constructor which
746.72 -> is actually really happening ‘beforeEach’ but it
doesn't matter. Once we've done the flush then
750.56 -> the data will be correct and then all that should
be filled in. So all we have to do now: save that
757.04 -> and we can see we've got eight successes. And
if we just look at the browser – remember, it's
764.32 -> ‘should get the correct reviews’ - and
we should be able to see we've got there,
768.08 -> ''should get the correct reviews'. So that's worked
correctly with the mock data. If we got anything
773.76 -> wrong about that, so if, for example, I were to
change the code here so it makes an incorrect
782.56 -> request - so let's just put an ‘s’ on the end
of that and save it we'll - see we get a failure
788.32 -> and the failure is 'expected one matching request
for criteria' and then it was 'Match by function'.
794.8 -> But we know what was in the function and we can
see it's not matching. So we can be very precise
799.68 -> and get that to fail if there was really
anything going wrong with all of that.
806.88 -> Let's just quickly do one more, because that
was looking at the GET, when we're trying
810.64 -> to make sure the data comes back. The other
example we had was for our POST. So remember
815.04 -> in our add-review-component,
when we click ‘addReview’
820 -> that calls ‘addReview’ on the repository, that
then now does a POST method to the book review for
826.16 -> all of that to work. And so that's what we need
to do in the add-review-component.spec - similar
833.76 -> sort of thing. So in fact, let's copy all of
that. So if we get hold of our all review spec,
839.84 -> get hold of that and put that into our
add-review-component in the same sort of place,
845.2 -> that will mean we need to get hold of that. Then
also we need to get hold of the actual object for
852.32 -> that. So remember that was that bit of code there
and in ‘beforeEach’. So in our ‘beforeEach’ we'll put
857.6 -> that one in there and then we need to actually
write our test. So again let's copy that one
865.36 -> and 'do a POST when review is added'. So let's then
set up the basic functionality. So in order to
878.16 -> add a review, remember the way this works, we
need to say ‘component.title’ so this, if we
885.76 -> just look at the code here, is our ‘addReview’
we've just got these two fields ‘title’ and
892.16 -> ‘rating’. So that's what we're setting up in a
test, rather than typing them into the GUI. So
897.12 -> we've got those. So we're going to say
‘component.title =’ and let's make that
901.92 -> one – well, it doesn't matter at all – ‘book-a’
and then ‘component.rating =’ give that a ‘4’
916.16 -> and then we're going to say
‘component.addReview()’.
921.92 -> And the way that works - addReview
takes no parameters, and if we just
925.04 -> look inside that you can see it picks up the
‘title’ and the ‘rating’ that I’ve just set up,
930.16 -> and then sends it off to the addReview in the
repository service. So very similar to what
935.52 -> you do in manual testing. You fill in one
of the fields, you fill in the other field,
939.12 -> you press the button. Basically, in code, the
same sort of thing that's happening there,
943.04 -> but of course notice we haven't even checked
for anything. We've got no 'expect' statement,
947.6 -> so it's not as yet a particularly good test.
And in fact in terms of the behaviour, all that
953.76 -> behaviour is really hidden away. All we can do to
test it is make sure that the correct calls are
959.92 -> made on the HttpClient. And again we do that in
a not dissimilar way. So what I’ve now got to do
965.6 -> is, after I’ve done all, that I’m going to once
again say ‘const request = httpTestingController’
974.56 -> and again we want only one of these calls to
be made, 'data' and then the arrow and then we
982.32 -> now want to make sure that one's correct. Well,
the URL is going to be exactly the same, because
990.16 -> it's still going back to the same URL on the
server. Because remember, this is a RESTful API
996.88 -> and therefore we have the same URL but a different
method when we want to do the different things.
1001.76 -> So if I just copy that and pop that in there,
so we want to make sure that that's correct,
1009.52 -> but then what we also want to do is make sure
that this one is a POST, so ‘&& data.method ===
1022.8 -> ‘POST’’. So that's checked the request. What
can then do is we can then do an ‘expect’
1028.96 -> and then we say ‘request’ and then slightly
confusingly we say ‘request.request.body’.
1036.48 -> And so what I want to do is make sure that the
body of this is correct - it's sending off the
1041.76 -> correct data. So I’m going to do a ‘.toEqual’ and
then what it should be sending off is, obviously,
1047.36 -> a BookReview for 'book-a' with a rating of
‘4’. So we can put in title of ‘book-a’
1056.88 -> and rating of ‘4’. And then once again we're going
to have to do this ‘request.flush’. Remember, that
1068.16 -> gives us the data that we're returning. Well on
a POST request, remember, what gets returned is
1073.44 -> the id of the newly created object. And we don't
really care about that. We know it's a number,
1077.84 -> so I’ll just put ‘1’ in there, we're not going
to worry too much. But having done all that,
1081.76 -> that should now work correctly. So if I save that,
then we've got an error. Anyone notice what I did?
1092.88 -> It's because I put ‘4’ as a string in
there, not as a number. Try that one again
1100.72 -> and you can see we've got nine successes.
So typical sort of thing you can make as
1104.48 -> a mistake. Easy enough to fix. So that's it. We
have now seen how we can complete that process of
1111.44 -> connecting up the Angular front end to the .NET
backend. Not only having the application work,
1116.72 -> but really just as importantly having the unit
tests work. And so we saw we needed to mock the
1121.44 -> httpClient, just to get it working at all. And
then we could use that to take precise control
1126.64 -> of our tests. So hope you enjoyed that. If you
did, do click like. Do subscribe. And next time
1131.52 -> we'll be looking at some other aspects of how we
communicate between Angular and backend server.
Source: https://www.youtube.com/watch?v=fODLFJ8D4VU