9. Testing Authentication
So we can do authentication, but what if the next time we turn around, this feature breaks? How would we catch it? The answer is more automated tests!
So it is really fun to test things right!?
Maybe not so fun, but writing tests is a critical skill for a young engineer. At almost all companies, all code must have tests that go along with it. And at many companies, young engineers are tasked with writing tests and crushing bugs for their first few weeks, so knowing how to test well will make you a very hirable and quick-to-ramp employee.
Testing Authentication
Testing RESTful routes for a regular resource is pretty straightforward. There might be some quirks, but pretty much you just GET
, POST
, PUT
, and DELETE
data and verify that the responses are successful.
With authentication, however, testing gets a little more complicated. You have to check that JWTs
are creating, that passwords are correct, and then you have to be prepared to test authorization.
The biggest challenge is tracking the cookie
set by the server when a user logs in. In order to handle this cookie
we can use chai
's request agent functionality to track the cookie in our tests.
[action] Create the file
test/auth.js
. Within, we'll require the libraries we're going to need.
// test/auth.jsconst chai = require('chai');const chaiHttp = require('chai-http');const { describe, it } = require('mocha');const app = require('../server');>const should = chai.should();>chai.use(chaiHttp);>// Agent that will keep track of our cookiesconst agent = chai.request.agent(app);>const User = require('../models/user');>describe('User', function () { // TESTS WILL GO HERE.});
We can now write our first test that verifies that you cannot login if you haven't signed up yet.
[action] Add this test within your
User
block:
it("should not be able to login if they have not registered", function (done) { agent.post("/login", { email: "wrong@example.com", password: "nope" }).end(function (err, res) { res.should.have.status(401) done() })})
Let's add a slightly more complex one: signing up a user!
[action] Add this
signup
test totest/auth.js
:
// signupit("should be able to signup", function (done) { User.findOneAndRemove({ username: "testone" }, function () { agent .post("/sign-up") .send({ username: "testone", password: "password" }) .end(function (err, res) { console.log(res.body) res.should.have.status(200) agent.should.have.cookie("nToken") done() }) })})
It's important to note that the server started by the agent
will not automatically close following the completion of your tests. It's important that we close down the agent after the tests to ensure the program exits appropriately.
[action] Add an
after
hook to yourauth
tests:
after(function () { agent.close()})
Do not forget to add
after
to your require statement.
Now Commit
$ git add .$ git commit -m 'Implemented initial authentication tests'$ git push
Before we go any further, try running your tests. Remember you need to kill nodemon
first and then run npm run test
.
Your auth
tests should work fine, but notice that your posts
test around creating a new post doesn't work anymore! This is because the way the test is currently written, you're not logged in and trying to post! Let's fix this before moving on.
Updating Posts Tests
Let's revisit our /test/posts
file and see what needs to be done in order to get it up and running again.
First, we want to make sure we're using the agent
that we used in our last auth
tests, otherwise we'll have no way to pass cookies (like the kind that pass login information)
[action] Add the following line to the top of the file as the last
const
:
const agent = chai.request.agent(app)
Now we need to make sure we have a user.
[action] Add the following
user
below thenewPost const
:
const newPost = {...}>// User that we'll use for testing purposesconst user = { username: 'poststest', password: 'testposts',};
Next we need to add a before
hook. Much like the after
hook cleans up our tests, before
sets up anything we need in order for the tests to run, before the tests actually run.
[action] Underneath your new
user const
, add the followingbefore
hook that signs up a user:
before(function (done) { agent .post("/sign-up") .set("content-type", "application/x-www-form-urlencoded") .send(user) .then(function (res) { done() }) .catch(function (err) { done(err) })})
Remember to require
before
frommocha
at the top of the file
Almost finished, just have to clean up now. Much like we deleted the test post in our after
hook, we have to also delete the test user now too, in addition to closing the agent
.
[action] Require your user model file at the top of the file
const User = require("../models/user")
Replace your
after
hook with the following code block:
after(function (done) { Post.findOneAndDelete(newPost) .then(function () { agent.close();> User .findOneAndDelete({ username: user.username, }) .then(function () { done(); }) .catch(function (err) { done(err); }); }) .catch(function (err) { done(err); });});
Try running your tests again. They should all pass now that the agent has the JWT cookie!
It's important to remember that when you add new functionality, it's helpful to go back and make sure your previous functionality (and tests) work as expected.
Now Commit
$ git add .$ git commit -m 'Fixed posts test'$ git push
Let's finish up our other tests back in test/auth
!
Testing Login and Logout
What should we test next? Logging in!
Next, write a test that verifies that your login implementation works properly:
[action] Add this
login
test totest/auth.js
:
// loginit("should be able to login", function (done) { agent .post("/login") .send({ username: "testone", password: "password" }) .end(function (err, res) { res.should.have.status(200) agent.should.have.cookie("nToken") done() })})
Can you make the test red (not pass) and then green (pass)?
Finally, we write a test to verify that the logout functionality works as expected.
[action] Add this
logout
test totest/auth.js
:
// logoutit("should be able to logout", function (done) { agent.get("/logout").end(function (err, res) { res.should.have.status(200) agent.should.not.have.cookie("nToken") done() })})
Now Commit
$ git add .$ git commit -m 'Implemented remaining authentication tests'$ git push
You have successfully implemented tests for authentication flows! If you feel like your tests aren't strong enough, and want to make your tests more robust (you know you want to), check out the challenges below:
Stretch Challenges
[challenge]
Can you write another test to test that it is impossible to create a post if a user is not logged in?
Can you make all of your
auth
tests not pass, and then pass? What about your newly updatedpost
test?