Heads up! To view this whole video, sign in with your Courses account or enroll in your free 7-day trial. Sign In Enroll
Preview
Start a free Courses trial
to watch this video
Let's take a swing at using Functional Testing of our API. We want to make sure we have status and the correct JSON response.
Code to copy
Related Discussions
Have questions about this video? Start a discussion with the community and Treehouse staff.
Sign upRelated Discussions
Have questions about this video? Start a discussion with the community and Treehouse staff.
Sign up
So it is time to write
some functional tests.
0:00
Are you ready, I know I am.
0:03
So typically these tests
are written to prove
0:05
to people who requested features that
things are operating as they expect.
0:08
So what we want to prove here is that
when someone makes a web request to
0:13
a specific URI or resource,
that certain actions happens and
0:18
the correct status codes are returned.
0:22
Right, so that's all we're
testing here functionally.
0:25
Now, what makes things a little hard
here in Spark land is that our routes
0:28
are lambdas as well as they accept
some of these carefully request and
0:32
response objects, right?
0:37
Some frameworks provide a handy
way to perform this testing.
0:38
But alas our little little
microframework here does not.
0:42
So like we often have to
do in microframework land,
0:45
we have to craft our own solution.
0:48
So let's start by making a test right?
0:50
So we'll come here to this API and
we will do command shift t.
0:52
We'll say create test, That's great, let's
go ahead and do an before and an after.
0:57
We know we're going to need
those later and we'll click OK.
1:04
Okay, so one thing I wanna do here
is actually start our sever up and
1:09
the way we do that is by calling
the main method right, in our API class.
1:13
We're gonna keep our server running for
all of these tests, all right,
1:17
we're not gonna start it and stop it.
1:20
So we're going to use
an annotation called BeforeClass.
1:22
So this will run once before any test
run and only once after it's all done.
1:26
So we're gonna call it BeforeClass and
it's going to be a static class, or
1:30
a static method sorry.
1:35
And it's gonna be called startServer
cuz you can name whatever you want,
1:37
we want to make sense.
1:40
So here we're just going to call that main
methods, we're going to say Api.main.
1:41
Okay and it takes arguments, and
right now we don't have any.
1:47
So we're gonna say null and
1:50
remember that's an array of
strings that that accepts.
1:52
So let's also stop it when we are done,
so we 'll say AfterClass.
1:56
And again we can name
this whatever we want but
2:01
I think it's probably best if we call
it stopServer, seems to make sense.
2:03
It's clear, anybody can look at that and
know what that is.
2:07
And there is a static method
off of Spark called stop.
2:09
And it's talking about,
we're talking about Spark Spark.
2:16
[SOUND] Okay, so let's take a quick
look at what this is gonna do.
2:20
If we flip over to our Api.java and
we look in here, this is the main method
2:23
that it's gonna call and it's gonna
create a connection to the database.
2:29
And it's the file database,
2:34
that means any time our test
runs it would write in there.
2:35
We don't wanna do that, that's no good.
2:37
We need some way to configure this.
2:40
You know what the other thing is?
2:43
This is always gonna run on port four,
five, six, seven, but
2:44
we know that if you try to run the server
while the server is running, it blows up.
2:48
So we can't do that,
we need to configure both of those.
2:53
So we need some way to do this.
2:55
Now just so you know,
there are great ways to do this.
2:58
There's third-party configuration
libraries out there,
3:01
Apache Commons Configuration
is the most popular one.
3:04
Check the teacher's notes for
some more of these.
3:07
But let's go ahead and
do something pretty simple here.
3:08
And later we can implement one of those
custom configurations if we wanted to.
3:11
So we do have a way to push
settings into our API and
3:15
that's through the arguments passed in,
right?
3:17
We can pass something in there because
we're calling it on the other side.
3:20
So let's do that,
let's come over back to the test.
3:24
And where we're passing something here,
let's go ahead and
3:29
let's make a new array here.
3:33
We'll say, string of and we'll call
it args, we call it whatever we want.
3:34
And let's go ahead and let's set the port.
3:41
We'll set the string, so we're gonna put
it in there, we'll say 4568 for the port.
3:43
And let's set up the jdbc:h2, we want in
memory for sure, we want that to be fast.
3:48
And we'll just name it
testing like we did before.
3:55
So, there's that and we'll pass args in.
3:57
You know what, this number looks
a little magic doesn't it?
4:01
Let's go ahead and
that's always a bad thing.
4:03
So let's go ahead and we'll choose
Refactor, and we will Extract, Constant.
4:06
And we wanna this PORT, and we'll go
ahead and also extract the constant here.
4:14
So we'll do Refactor, Extract, Constant.
4:23
And we are going to call this
TEST_DATASOURCE just to be super clear.
4:28
Cool, and if we look up at the top
here those are exposed and
4:36
now somebody reading this understands
what it is that we are doing.
4:39
And you can change it in one place
should we use this elsewhere.
4:41
Okay, so now in our API class we have
some arguments that are passed in right?
4:46
So why don't we right
here say if args.length,
4:51
cuz you don't have to pass anything in and
if we don't we want it to run like normal.
4:55
So if there are args.length we know that
the first one is the port, right, so
5:02
we'll say port.
5:07
And we will do Integer.parse,
because we passed in a string, args[0].
5:09
And port is a static method off of Spark.
5:16
So, that will set the port
that the server runs on.
5:22
Now the second parameter is a little bit
trickier and actually, why don't we move
5:25
this right up by the arguments,
that's kind of the best practice.
5:28
So let's move that above these guys here.
5:32
The second argument is the data source,
right?
5:36
So, we're gonna go ahead and
let's set what the default data source is.
5:38
So, I'm gonna grab this.
5:42
And we will pop that up here,
we'll say String datasource.
5:45
Okay, so since there are two,
we're gonna set specifically
5:54
the data source to what was
passed in if it's there, right.
5:59
Now do you see anything wrong with this
code, maybe we need to be more careful.
6:04
So if something's passed in and
args.length isn't equal to 2,
6:08
they didn't pass both the parameters.
6:13
We need them to pass it,
otherwise we'll blow up here.
6:16
So we'll do a, we'll tell them how to use
it better, right, usage instructions.
6:18
Let's say java Api, and we're expecting
a port, and we're expecting a data source.
6:23
And this is just kinda
what we'll print out
6:28
on the command line if they try to
press something there and let's exit.
6:31
Why keep going if we haven't
passed in two arguments.
6:35
Cool, so now we'll use some string
formatting here to our benefit.
6:40
We'll say String.format and
we'll put back in that data source, right?
6:44
Boy, there's a lot of quotes
going on in here, isn't there?
6:51
Always a little bit dangerous there, so
we're gonna pass on the datasource and
6:54
then finish that off.
6:58
There we go, that looks good.
7:00
Okay, we're getting so close.
7:07
So if we flip back over to the ApiTest,
and
7:09
we'll jump back into our before method.
7:11
Now remember that we need to
keep the connection open or
7:13
the database will be erased each time.
7:16
We don't wanna do that.
7:18
So, what we should do is we
should make a new connection and
7:19
open it up and keep it open for each test.
7:21
And then therefore when the test is over,
the database will get erased.
7:24
It'll work kinda like magic, right?
7:27
So we'll say, Sql2o sql2o = new Sql2o.
7:28
Hey look we get to use our
TEST_DATASOURCE there.
7:36
And we want to also make sure
that we do that INIT again.
7:41
Probably should put this in
function someplace right?
7:48
I feel like I've written it enough,
RUNSCRIPT from,
7:50
we wanna remember both,
classpath:db/init.sql.
7:54
And we wanna remember that final
trailing quote there, awesome.
7:59
And then we're gonna open up a connection
that we can close, sql2o.open.
8:05
This is going to be a private field.
8:16
That is fine.
8:21
All right, and
then we want to close it after each test.
8:24
So remember the before class is
running before all the tests run, and
8:28
this is before and after are running,
before each test and after each test.
8:31
Awesome, okay so it's about time to craft
our own objects to manage calling our API.
8:36
Remember, Spark does not come with this
out of the box yet, maybe it will.
8:44
So we'll need a way to test requests and
responses.
8:48
So why don't we start
with the response first?
8:52
I think that's probably the most
clear one that we can do.
8:54
Now we can add files in our
testing bit over here right.
8:57
So this is the main but
9:00
if we come down here in the testing
we can add a new class in here.
9:01
So let's do that, let's do
com.teamtreehouse.testing.ApiResponse,
9:05
so this will be the response
that comes back.
9:11
And really all that we care about is,
we want to know about the status, right?
9:16
And we want to know about the body of it,
what does the body from the response say.
9:22
Don't really care about too
many more headers do we.
9:30
So, let's go ahead and
9:33
generate a constructor, both of those,
9:36
and let's generate some getters for
9:41
both of those, there we go.
9:46
You know what, these shouldn't
change ever so let's not let anybody
9:50
mess with the response, so
we'll make these final as well.
9:53
Okay, so after they're initialized
they can't change, cool.
9:57
All right, and now, we're at one of those
places where there is quite a substantial
10:01
amount of finicky code to write.
10:06
It's a bit out of scope, but
I want you to feel like you can do this.
10:08
There's nothing magic here.
10:11
I just kind of don't want
to have you watch me type.
10:12
So let's do this, in the teacher's notes,
there is a link to some code,
10:14
and you should go grab it.
10:18
And what we're gonna do is we'll
make a new file right in here.
10:19
We'll make a new Java class and
we're gonna call it ApiClient.
10:23
And from here I want you
to just copy the code and
10:29
paste it in and we'll walk through it.
10:34
All right, so I've got it copied and
I'm gonna paste it.
10:36
Okay, so let's walk this code.
10:39
So when we're creating our server,
what you do is you pass it in.
10:42
And that's gonna be like our
local host and our port.
10:45
We'll do that when we create it up.
10:47
And this is an overloaded method
because we are not pulling in the body.
10:49
I'm gonna close this so we have more.
10:54
So we're not, you don't always
wanna push in the body, right.
10:56
The body is only for posts, usually.
11:00
So this request here is
the chunky bit of code.
11:04
So here we go.
11:06
So URL comes form the Java.io package, and
here we're just going to build it, right?
11:07
We're gonna build the URL.
11:12
We're gonna concatenate
the server that we know, and
11:13
we're gonna take the URI
that was passed in here.
11:16
So that's /courses, right?
11:19
And we're gonna open up a new connection,
okay?
11:22
And the way that connections work is you
open them and then you set things on them,
11:27
and then you call connect.
11:31
So, we are going to open from the URL and
create a new connection.
11:34
We're gonna say that it's a post or a get.
11:39
We're gonna tell it that no matter
what we're setting this header here,
11:40
we're setting that the content
type is application json.
11:44
And then if we have passed
in the request body,
11:47
we need to let the connection
know that it needs to do output.
11:50
It's a little wacky
what you're doing here.
11:56
So then, what we do is we open
up a connection stream and we're
11:57
going to put it in the try with block
there, so it will close when it's done.
12:02
And we write out to it, just in case
there's any unicode characters,
12:05
we want to write out of
all the bytes there, so
12:09
that we can get umlauts and
accents and all those things.
12:11
This line here actually
makes the connection happen.
12:17
And then what happens is we'll get back
either an input stream or an error stream.
12:20
So remember this is using
a ternary operator.
12:24
So it's saying if this is true,
12:27
return the input stream,
otherwise return an error stream.
12:29
And what this is here is saying
anything with a status code
12:32
that is less than 400 is good to go.
12:36
That's a good thing.
12:39
Anything above 400 is an error.
12:40
Remember that from HTTP?
12:42
IOUtils here comes from Spark and
12:44
it's going to take our stream and
turn it into the string that we need.
12:47
And then, of course, we're going to
make one of those API Responses.
12:50
And return it back.
12:54
Whew!
12:56
And then if anything goes wrong,
we'll kick up a RuntimeException so
12:57
that the test can catch it.
13:01
There we go.
Let's see.
13:03
It looks like I forgot to paste the last
trailing to close the client there.
13:06
Okay, so let me know what you
think about this approach of
13:10
pasting versus slogging
through writing this code.
13:14
But the good news is this is written.
13:17
Okay so we can use this now, right?
13:18
So why don't we make it available
to all of our tests, right?
13:21
So we should put that in the before here,
right?
13:24
So we'll go ahead and
we'll say client = new ApiClient and
13:26
we're gonna pass in http://localhost,
13:33
and once again we're
gonna use our constant.
13:39
Cool, so, client,
we need to make a field for this guy.
13:44
Create a field client and
we have an ApiClient that we can use.
13:49
Phew, right, so let's go generate a test.
13:53
So, let's make sure that when we
create a new course that it returns
13:57
the proper status.
14:02
So we'll make a new test and
14:03
we will say
addingCoursesReturnsCreatedStatus.
14:06
And we saw in postman that this is
working, but if we write a test for this,
14:13
we'll always know that it's working,
right?
14:16
So writing JSON strings
is a bit of a bummer, so
14:18
I typically like to make a map and then
turn that into JSON using our buddy GSON.
14:22
So we're gonna make a new HashMap, and
14:31
that is going to have some tests,
put the values in there.
14:35
So we will do Tests and,
I forgot to do values.put and
14:41
we want to put those values in there.
14:46
And we will scroll up a bit here and
we will say values.put.
14:52
And we'll put in url and
we'll give it our famous URL.
14:57
We start doing more of these,
we can always move this around.
15:02
So now we have prepared,
arranged our values, and we're gonna act.
15:05
We're gonna say ApiResponse, and
15:11
we'll get back a response object,
and we'll say client.request,
15:15
and this is the thing that we just wrote,
we want to post that's method.
15:19
The URI is courses, and
15:23
the body is going to be
a gson.toJson(values),
15:26
and of course it doesn't know what gson is
because we haven't talked about that yet.
15:31
So let's go ahead and
we will say gson = new Gson();.
15:36
So it is a JSON and
we need to make a field for that.
15:44
There we go.
15:50
We will now check that 201
is returned from our status.
15:58
Here we go, what do you think,
is it going to work?
16:12
Let's make sure.
16:15
So we will switch to
All in course-reviews,
16:17
this is being a test for that.
16:19
And we will go run.
16:21
And our API test fails.
16:25
No initial, need to specify
a class name or system property.
16:28
I think I forgot.
16:35
And this Sql2o,
I forgot the username and password.
16:39
That is such a confusing API that
it doesn't, I always forget that.
16:42
I apologize.
16:46
That's the error that happens when
you do that, just so you know.
16:46
I have now done that twice.
16:50
Sorry about that.
16:52
Learn from your mistakes.
16:53
There we go.
16:56
So here it is.
16:57
It worked.
16:58
Awesome.
16:59
So what else can we test now
that we have a valid test?
17:01
So we need to get a course in place.
17:04
So let's go ahead and
17:05
let's copy our course from the other
test that we have in place.
17:07
Pull up that new course.
17:12
So, over here, in courses,
if we come in the DAO, and
17:16
we come in this DAO test,
we will copy this new course method.
17:20
Now, if we end up doing
this too much more,
17:28
we might wanna think about
putting this elsewhere.
17:30
So, we'll add a new, and
that creates a course.
17:32
And let's add a new test now.
17:37
So we're gonna test that courses
can be accessed by ID, okay?
17:38
So do a new test method and we'll say
17:44
just like we said
coursesCanBeAccessedById.
17:49
All right, so we'll make Course
course = newTestCourse(); and
18:00
we will add it, courseDao.add(course).
18:08
And we need to make a new courseDao,
don't we?
18:17
So we will do courseDao
= new Sql2oCourseDao.
18:22
And it takes a sql2o.
18:31
There we go, and we'll make this a field.
18:34
Cool.
18:42
So now we have an added course in
our test, and let's go hit and
18:43
make sure that we can get it.
18:47
So we'll do an ApiResponse
res = client.request.
18:49
We're gonna do a GET this time and
18:55
we are going to get back /courses/.
19:00
Let's do this on the next line
"/courses/" + course.getId().
19:05
So this is where you're using that method.
19:11
And we're going to pull back from gson,
let it do the heavy lifting.
19:16
We're gonna retrieved back from
the body there, or gson.fromJson.
19:21
So the body that is set in
the response there, getBody,
19:28
should be able to populate
our Course.class.
19:32
Then what we'll do when we assert,
we will assert that
19:36
basically those are the same
thing using the equality method.
19:41
So we're expecting the course and
retrieved is what we're checking.
19:46
So we'll see if those are both the same.
19:49
There we go.
19:55
All right so we know that that worked too.
19:57
Awesome.
So both of those are working,
20:02
so let's do one final test.
20:03
Let's see what happens
when we say missing course
20:06
MissingCoursesReturnNotFoundStatus, right?
20:15
So remember that was the 404 thing we did.
20:21
So we'll just go ahead and get a response.
20:24
We'll act right away because we don't
care if there's anything out there.
20:28
And we'll say request GET And
20:31
we will just look for courses 42.
20:37
And that should return a 404,
we know that it doesn't.
20:40
But let's go ahead and we'll write
a test that proves that it doesn't.
20:46
And then once we fix it,
it will always work.
20:51
Okay, so let's run, we got a red error.
20:53
Expected 404 but got 200.
20:59
Let's fix that.
21:01
>> Now that we got our testing all set up,
21:02
we can start to feel better
about the validity of our API.
21:04
Now, I realize that was intense.
21:08
And most frameworks have that
sort of testing built in.
21:10
The good news here is, we are most of
the way to writing a fairly generic way of
21:13
functionally testing Spark applications.
21:18
We could, in fact, extract that and
refactor it into an open source project so
21:20
that others wouldn't have
to go through that pain,
21:24
as well as they could extend and
improve it.
21:26
Now another important thing to we test,
21:29
which we won't have time to get into here,
is load testing.
21:30
Since you're publishing
your API to the public and
21:33
anyone could be using it,
you wanna know how much load it can take.
21:36
Spark does an amazing job of handling
many connections at once, but
21:40
you really should see where it falls over
and can no longer take care of requests.
21:44
As you saw,
21:48
we're getting super close to having
a really nice API that we can iterate on.
21:49
I think we should really
fix that failing test.
21:53
And make sure that when an error happens,
21:56
we send the correct error message and
HTTP status code from our API.
21:57
Let's go turn that red to green.
22:03
You need to sign up for Treehouse in order to download course files.
Sign upYou need to sign up for Treehouse in order to set up Workspace
Sign up