In this post we’ll be continuing from where we left off with part 1. We have a good chunk of the Meteor tutorial completed along with associated tests. In Part 2 we’ll finish the tutorial and deal with handling users and authentication within our tests.
So onto step 9…
Implementing Step 9 of the tutorial
Step 9 requirements
- The application should allow users to create accounts.
- Only logged in user's can create new tasks.
- Tasks should indicate who created them.
Writing our tests and implementing step 9
OK, since we are adding the concept of users to our application we’ll need to update page-contents
to take into account a login / logout link.
As well, new-task
will need to include the new rule about logged in users being the only ones able to create tasks. Also we’ll want to enhance the test to check that tasks are correctly associated with the users that created them.
Finally we need to update task-item
as tasks now display who created them in the UI.
A bit of set-up is going to be required before we can get to the actual testing. We need to figure out how to handle accounts within tests. First off let’s add the necessary authentication packages.
Terminal
Now that we have the accounts packages installed, we’re going to want to go about creating some user fixtures.
Terminal
/packages/testing/user-fixtures.js
Here we’re doing two things, Accounts.removeDefaultRateLimit();
is necessary to avoid hitting the default rate limit on accounts when running our tests.
Then we are setting up the user we’ll be using for our tests, we search for ‘Bob’ and create him if he doesn’t already exist.
We need to update our package file to include the new fixture.
/packages/testing/package.js
We’ve added user-fixtures.js
to our list of files and now as per Robomongo we’ve got a user in our database.
Next we’re going to need a way to login and logout from within our tests. We could interact with the UI, but we’re going to circumvent that by just calling the appropriate Meteor methods.
So we’ll create a new file for this purpose in our testing package.
Terminal
/packages/testing/test-user.js
Perfect we now have methods for logging in and out which we can call from within our test code.
Again we need to update package.js
.
/packages/testing/package.js
We’ve added the new file and exported the TestUser
class.
Finally let’s alter our task fixture to automatically assign a user to a task when a user is logged in and default to ‘Bob’ when a user isn’t signed in.
/packages/testing/task-fixtures.js
We’re checking if a user is logged in, if so that’s who the task gets associated with, otherwise we are going to default the owner to ‘Bob’. We could omit the else block where we assign the task to ‘Bob’ when a user is not logged in… but automatically assigning a user is going to make the before
blocks in our tests less bloated, we don’t have to worry about always logging in just to get our task data associated with a user.
Sweet, now we can get onto the actual tests. Let’s start by checking for the login / logout link and that the new task field only appears for logged in users.
page-contents-spec.js
Tests
/test/jasmine/client/integration/todos/page-contents-spec.js
OK, a decent amount of changes. First off we’ve wrapped our existing tests in a new for all users
describe block. Notice that we’ve also removed the test that checks for the new task field as the field should only show up for logged in users. The tests within for all users
test for items that… you guessed it, should appear for all users regardless of whether they are logged in or not.
Next is the for logged in users
describe block for tests specific to logged in users.
First off we have a before
block which logs in our test user, notice the different syntax that needs to be used when interacting with the API of a debug package, we need to prepend the call with Package.<package name>
, instead of just calling TestUser.login
so the call becomes Package.testing.TestUser.login();
.
Our for logged in users
tests check that a logged in link exists consisting of the user’s name and that the new task input is available.
Conversely the for logged out users
tests contain a call to logout
in the before
block and then checks for a login link and that the new task field does not show up.
With these changes we have 3 tests failing.
The test that checks for the presence of the new task field is going to pass as that field currently always appears as we have yet to hook up the logic to hide it for logged out users.
Implementation
Let’s get these tests passing. First thing is that we’re going to be logging in via username instead of email so let’s set up our accounts config.
Terminal
/client/config.js
Now we’ll update the HTML.
/client/templates/simple-todos.html
We’ve added the loginButtons
template and also wrapped the new task field in a currentUser
conditional.
And with that we have our page-contents
tests passing, but we seem to have some issues with the new task tests now.
This makes sense, we are going to need to login in order to have access to the new task field, so let’s update new-task
. While we are at it, we’ll update new-task
to check that tasks are associated with the correct user on creation and that the task text is prepended with the username.
/tests/jasmine/client/integration/todos/new-task-spec.js
We’ve added beforeAll
and afterAll
blocks to handle the login and logout, these blocks are similar to beforeEach
and afterEach
except that instead of running before and after every test, they run once before all the tests are executed and then once after all the tests are complete. This is a handy way to login prior to all our tests and then logout afterwards.
We’ve also updated the expected task text that will be displayed in the UI, i.e. expect(tasks[0]).toEqual('Bob - My new task');
. Tasks should now display both the text of the task and the name of the user who created them.
Finally we’ve updated our DB checks to ensure the username and owner values are correct via expect(task.username).toEqual('Bob');
and expect(task.owner).toEqual(Meteor.userId());
.
To get everything passing we’ll need to update the code that displays the task text.
/client/templates/simple-todos.html
And we’ll need to add the user fields to the addTask
method.
/lib/collections.js
We’re now setting the owner and username on inserted tasks.
And now our tests should be passing!
Drat! Looks like we have some work to do, we’ve gone backwards. Let’s look at the first failure.
The issue is we’re still using the old format of the task text in our test, we need to include the user name as part of the text, so we’ll be changing the following test in task-item
:
We’ll rename the test and alter the second expect
to include the username.
/tests/jasmine/client/integration/todos/task-item-spec.js
OK, that gets us down to two failing test, both in task-list
:
So same issue that we had in task-item
, a similar change is required to get things back passing.
/tests/jasmine/client/integration/todos/task-list-spec.js
We’ve just updated all instances of the task text to be prepended with Bob
, and with that we are finally back to a passing state.
Onto step 10.
Implementing Step 10 of the tutorial
Step 10 of the tutorial is where the insecure
package is removed and everything is moved into Meteor methods. We removed insecure
off the drop so are ahead of the game. There is one minor requirement we can test for however.
Step 10 requirements
- Attempting to add a task without logging in should throw a 'not-authorized' exception.
Writing our tests and implementing step 10
This test is going to be a little different than the others we’ve written, as we’re going to need to bypass the UI. This is because the new task field isn’t available in the UI unless a user is logged in and this is exactly what we need to test (i.e. that a non-logged in user cannot add a task). So what can we do?
Well one thing we could do is to call our server method directly from within our test. Something like the following could be done from the new-task
spec:
There are a few problems with this approach.
First off since we’re just calling a Meteor method directly it doesn’t seem like much of a client test, we’ve got no client going on here!
Second, although it’s a valid, passing test we get a nasty error showing up in the browser console due to the not-authorized
exception that gets thrown by our method. Although we are checking for this exception in our test it still is going to propagate to the browser console. This is ugly as it means our browser console is getting populated with cruft and it’s going to be easy to overlook any new errors hitting the browser console down the line.
So we are much better off testing this kind of thing with a server test, so let’s get that hooked up!
Tests
We’ll need to create a new directory for our server tests along with a file to hold the tests.
Terminal
And now for the test.
/tests/jasmine/server/integration/tasks-spec.js
We now have a failing test.
Implementation
To get our tests passing we need to ensure a user is logged in before a task is added.
/lib/collections.js
And with that we are all good.
Sweet, we’re ready for our final step.
Implementing Step 11 of the tutorial
Step 11 of the tutorial deals with removing autopublish; we’re already set there, as we removed autopublish right off the bat. There are also a number of enhancements that are made to the application in step 11 however, so let’s have a look at those.
Step 11 requirements
- The owner of a task should be able to mark a task as private via a private / public button.
- The private / public button will display the current state of the task, i.e. it will display 'public' for public tasks and 'private' for private tasks.
- Private tasks should display in a way that makes it obvious they are private.
- Attempts to mark a task the current user does not own as private will result in a 'not-authorized' exception.
- Private tasks will only appear to the owner of those tasks.
- Private tasks can only be deleted by their owner.
- Attempts to delete a private task that is not owned by the current user will result in a 'not-authorized' exception.
- Private tasks can only be marked as completed by their owner.
- Attempts to mark as complete a task not owned by the current user will result in a 'not-authorized' exception.
Writing our tests and implementing step 11
OK, so we’ve got a good chunk of work in front of us to get this all implemented, let’s get started!
new-task-spec.js
We’ll start with some of the simpler changes and get those out of the way first. One change we’ll want to make is to new-task
to check that by default newly created tasks are public.
Tests
/tests/jasmine/client/integration/todos/new-task-spec.js
We’ve just added a new expect
statement, expect(task.private).toBe(false);
.
And now we have a failing test.
Implementation
/lib/collection.js
A fairly easy change, we’ve just added the default value of private: false
to the insert method.
And we’re back to passing.
While we are at it, let’s update our getDefaultTask
method in the task fixture.
/packages/testing/task-fixture.js
Again a very small change, just including private: false
as part of the default.
task-list-spec.js
Next we’re going to deal with the display aspects of private tasks, namely that they should not appear for user’s who don’t own the task or for user’s who are not logged in. We’ll also ensure that private tasks a user owns show up for them.
Tests
First off we are going to need another user in order to test our scenarios, so let’s set that up.
/packages/testing/user-fixtures.js
We’ve added a new user Sally
to our fixtures.
Next let’s update our login
and logout
methods.
/packages/testing/test-user.js
Here, we’ve extracted our login logic out to a function performLogin
; and we’ve created a new method for logging in with Sally. Our existing method logs in with Bob as usual. If you needed a whole bunch of different users it would likely make sense to have a single TestUser.login
method that takes in a username, but since we only have 2 users, I think a default login method along with one specifically for Sally works pretty good… and it also means we don’t need to change any of our existing tests.
With that out of the way let’s make the changes to the task-list
spec. There are a fair number of changes, so let’s build it up step by step and then we’ll list the full spec after we’ve completed the changes.
So the first thing we’ll want to do is create a new describe ("private tasks"
block for tests specific to private tasks and wrap our existing tests inside a describe ("public tasks"
block.
/tests/jasmine/client/integration/todos/task-list-spec.js
OK, so we’ve just created an empty block for the private tests and wrapped the existing tests in a public
block.
Next let’s flesh out the skeleton for the tests we want in our private block.
We’ve listed out the scenarios we want to test for, notice we’re using xit
instead of it
, this tells Jasmine we don’t want it to run these tests. It’s a useful feature when you want to jot down some scenarios but aren’t yet ready to code up the tests.
Now that we know our test scenarios, it’s easy for us to see what we need in terms of data. One public and one private task should be sufficient. So let’s fill in our test data and the contents of the tests.
Nothing complicated in our before
or after
blocks, we’re just creating one private and one public task.
Now let’s ensure the private task doesn’t show up when no one is logged in.
With this test we first make sure we are signed out, and then in the expect
section of the test we ensure only the public task is displayed. Note we need to wrap the logout()
call in it’s own timeout. Without this the expect section for this test executes before the user is logged out, causing the tests to fail.
Next we’ll test that the private task that Bob
owns doesn’t appear for Sally
.
OK, here we’re making use of our new user Sally
, we login as her and ensure she is unable to see Bob's
private task.
Finally let’s make sure that Bob
can see both of his tasks.
Simple, we login as Bob
and make sure both tasks are visible.
The full listing for task-list
is below:
/tests/jasmine/client/integration/todos/task-list-spec.js
And we see 2 of our 3 new tests are failing. The third new test where we check that Bob can see all his tasks will pass as that is the current behavior of the application.
Implementation
/server/publications.js
We’ve updated our publication to take into account whether a task is private or not and whether the current user is the owner of the task… and bam, with that simple change we are back to a passing state.
task-item-spec.js
Next let’s tackle the task-item
spec. Once again there are a decent amount of changes so we’ll build things up step by step followed by the full code listing.
We’re going to want to check that the private button shows for tasks the user owns, and doesn’t show for tasks the user does not own. We’ll also check that private tasks are displayed differently from public tasks… so let’s get started!
Tests
First off let’s create our new describe
blocks with some sketched out tests and wrap our existing tests in a describe
.
/tests/jasmine/client/integration/todos/task-item-spec.js
OK, so in our first describe / test we are going to check that the private / public button shows up for tasks owned by the current user.
Conversely, the second describe / test is going to check the opposite; that the private / public button does not show up for tasks not owned by the current user.
The third describe / test block is going to be used to ensure private tasks are displayed differently than public tasks.
Finally we wrap all our existing test code in a describe ("any task"
block.
Let’s start filling in our tests and then we’ll work on getting them passing.
First off we deal with tasks the current user owns.
In the beforeEach
block we login, and then create a task… this will result in a task being created which is owned by the current user. In our test we are then checking that the public / private button appears in the UI.
Next, let’s write the scenario for tasks the current user does not own.
This describe / test block is pretty much identical to the previous one, but this time we explicitly assign the owner
of the task we are creating to someone other than the current user. In our test we are then ensuring the public / private button does not appear.
Finally let’s test the display of private tasks.
We create a private task owned by the current user and check that it has the appropriate class.
The full listing is:
/tests/jasmine/client/integration/todos/task-item-spec.js
We now have two failing tests… the second test where we check that the public / private button does not appear is going to currently pass as in our implementation we don’t currently have the public / private button coded up.
Implementation
Let’s get these tests passing, first we’ll update the HTML.
/client/templates/simple-todos.html
We’ve updated the class for the li
tag, (i.e. <li class="{{#if completed}}checked{{/if}} {{#if private}}private{{/if}}">
) to include a private
class in cases where the task is private, this will address our test that checks that private
tasks are displayed differently than public tasks.
The other change is to add the private / public button on the task if the current user is the owner of the task.
We need to add the isOwner
helper.
/client/templates/simple-todos.js
And with that all our tests are back to passing.
update-task-spec.js
With update-task
we’re going to want to ensure that the private and public buttons work as expected. We’ll add a new describe
section for this purpose.
Tests
Let’s sketch out our new describe
blocks and tests.
/tests/jasmine/client/integration/todos/update-task-spec.js
We’ve set up a describe
block for testing the public and private task functionality and inside of that we have separate blocks for private v.s. public. Then we have 2 tests in each block which check that the task is updated in the database and that the text of the button changes.
Let’s fill in our tests and test data.
First thing we’ll do is set up login / logout in beforeAll
and afterAll
blocks for all the private / public tests.
With the public tasks we create a single public task as the test data.
In our tests we click the private / public button and in the first test ensure that the task is set to private in the database; in the second test we ensure the button text has changed to reflect the status of the task.
We’ll do essentially the same thing in our private tests. The difference being we’ll start with an initially private task which we’ll set to public.
The full listing for update-task
is:
/tests/jasmine/client/integration/todos/update-task-spec.js
As a result of the changes to the test, there are now 4 failing tests; 2 for the public tasks and 2 for the private tasks.
Implementation
To get the tests working we need to add an event handler for the private / public button.
/client/templates/simple-todos.js
And add the setPrivate
method.
/lib/collections.js
Now we’re back to passing.
tasks-spec.js
The final bit of functionality we want to test for is that we’re enforcing our access rules on the server. So we’ll add a few more server tests.
Tests
Again, let’s start with a sketch of what we want to test. We’re going to re-arrange our describe blocks a bit in the process.
/tests/jasmine/server/integration/tasks-spec.js
OK so we’re going to need to deal with users again, as we’ll need a current user and to create a task that the current user does not own.
On the client we’ve been using our test package to handle logins, i.e.
This won’t work on the server however as under the hood our test package calls Meteor.login(...
which is only available on the client. So how do we manage users when writing server tests? Well we can use Jasmine spies. I was a little confused by spies initially, until I read the docs (duh, always a good idea) and realized they are very similar to test mocks. I’ve used Moq a fair bit in .NET, and if you’ve previously used a mocking framework you’ll have no trouble understanding Jasmine spies.
The core difference between mocks and spies is that instead of replacing an entire object as with a mock, a spy only replaces the parts of an object you’ve attached a spy to.
So what does a spy look like in Jasmine?
What this means is that during the execution of ... some test
whenever Meteor.userId()
is called, the return value of that call will be someUserId
.
Making use of this we can set up appropriate scenarios for our tests pretty easily.
In the beforeEach
call we create a private task and directly assign an owner of someUserId
. Notice the callback… we need to store the taskId
for usage in our tests.
Next let’s have a look at the first test.
This is where we make use of our spy
, specifying that Meteor.userId()
should return someOtherUserId
. Since the ownerId
from our task (someUserId
) doesn’t match the value that will get returned from the spied up Meteor.userId()
call, we have set up a scenario where we expect a not-authorized
exception to be thrown.
After setting up the spy, we can get into performing our test by calling into the setCompleted
method, passing in the taskId
we’ve stored from the beforeEach
callback.
We’ll do a very similar thing for the other tests. The full listing is below.
/tests/jasmine/server/integration/tasks-spec.js
And we now have three failing tests.
Implementation
To get our tests passing we need to add some security checks to the methods.
/lib/collections.js
And with that we have success!
Summary
So hopefully these 2 posts have provided a bit of an introduction to Velocity and Jasmine.
It should be noted that there are other ways of using Jasmine to test applications, for instance this article makes extensive use of mocks.
My impression of Velocity is that it works pretty good and with some minor improvements / changes could be very solid. One of the main issues I see is speed. Once you have a large set of tests built up against a non-trivial application I don’t think watching and re-running all the tests on each file change is going to work well. An option to watch only certain files / specs would be useful, along with the ability to run tests on demand from the command line similar to RSpec.
It feels like a bit of a lost opportunity that for whatever reason the Meteor Development Group wasn’t able to leverage the talents of the Velocity team and more fully integrate Velocity into Meteor. I hoped the testing story for Meteor would really emerge and get stronger this year but it feels a ways off especially with the recent Velocity developments. I could understand someone passing over Meteor for a framework that has the testing story more solidified.
Next steps
Given the newly orphaned state of Velocity it’s a little hard to say if it is a good idea to rely on it going forward. How brittle it is to changes to Meteor? Will a future update to Meteor break Velocity? It seems possible and with no one maintaining Velocity, breaking changes might stay broke.
With that in mind, keeping an eye on the Meteor Guide recommendations for testing is likely a good idea, although at this point the guide is still a work in progress.
Another work in progress is The Meteor Testing Manual. I think eventually this will be an excellent resource, it’s written by one of the authors behind Velocity.
Another project from the Velocity authors is a testing framework called Chimp. Looks to be worth a look, I haven’t checked into it as of yet so can’t comment on how easy it is to handle test data etc. with it.
Some other frameworks to consider:
- Starry Night - A testing and scaffolding framework.
- Meteor Capybara Rspec - A Meteor specific fork of RSpec / Capybara.
- Gagarin - A Mocha based framework.
And of course there is tinytest for unit testing, this Meteor Cookbook link has some general information and further links about tinytest.
So a plethora of options… I imagine there will be some convergence at a future date.
References
A couple of resources that helped with putting together this post include:
- The Meteor Jasmine documentation.
- One of the first tutorials I saw on using Jasmine with Meteor.