Front-end Testing for the Lazy Developer with CasperJS
If you pride yourself on possessing the 3 Virtues of a Great Programmer, then you probably hate writing automated test suites. Writing comprehensive suites of Unit tests flies directly in the face of everything we Great Programmers stand for. Why would a lazy, impatient developer who never makes mistakes need to waste all that time, anyway?
Most of the time, all you want is an automated way to make sure your application is functioning properly at a high level. Do you really need to write mountains of Unit tests to do this? Can Unit tests even achieve this at all? How do you even write effective tests for front-end behavior?
Fortunately for you, virtuous developer, it's 2013 and there are some awesome tools to help you achieve your testing goals without wanting to pull your hair out or strangle some poor, pontificating TDD extremist.
CasperJS is the answer to the prayers you would have prayed if you weren't too lazy and impatient to bother. But first, a word about Unit Testing versus Functional Testing.
Try the customer support platform your team and customers will love
Teams using Help Scout are set up in minutes, twice as productive, and save up to 80% in annual support costs. Start a free trial to see what it can do for you.
Try for freeUnit Tests vs. Functional Tests
Let's review. There are basically 2 kinds of automated tests: Unit and Functional. Unit tests are written from the developer's perspective, and typically target a method or a class (did my sort
method sort properly?). Functional tests (sometimes referred to as Integration tests by those who prefer the extra syllable) are written from the user's perspective, and usually test the interaction between multiple building blocks of the application (is the user able to log in? Can she update her account?).
In a perfect world, your application would have both a comprehensive suite of Unit tests as well as a bunch of Functional tests exercising your application's features. In reality, you've probably got very little to none of either. If you had a full set of Unit and Functional tests, you probably wouldn't be reading an article title "Front-end Testing for the Lazy Developer" now, would you?
Functional Tests > Unit Tests
Let's face it. Writing Unit tests just isn't very fun, and despite what the idealist proponents of TDD may say, it can sometimes take just as long to write the test as it did to write the actual code. And will your users be impressed that you have 90% code coverage and that all of your unit tests are passing when you have pages in your app that show up blank or errors that render your application unusable? No one cares that your Unit tests are passing if your application doesn't function.
Unit testing makes sure you are using quality ingredients. Functional testing makes sure your application doesn't taste like crap.
Programmers solve big problems by breaking them down into smaller problems. And as anyone who has ever worked on a non-trivial application knows, the complexity lies in the interaction between the pieces of your application, not in the pieces themselves (if that's not true, you need smaller pieces).
You may catch some bugs and catch them early with your Unit tests. Great! But bugs feed off of complexity, which means you'll find them where things are the most complicated - in the interplay of your application's various parts. Unit tests can't help you there.
Especially in the Browser
Unit testing is even more challenging in web applications. Since your JavaScript code is mostly concerned with manipulating the DOM, you're going to need some markup to go along with your JS tests. Unlike on the server, where you can just fire up a JUnit test, instantiate some objects and call some methods, on the client you need to have a special page or pages set up just for your testing. I find that I usually end up spending more time setting up dependencies for my JS objects and making dummy elements than I do actually writing Unit tests.
So in addition to the downsides and limitations of Unit tests generally, on the front-end you have extra overhead involved in writing time-consuming Unit tests that will never ensure your application actually works.
Functional testing lets you test large swaths of code with considerably less effort and looser coupling. A Functional test tests that entering username and password into a form and clicking submit results in a successful login. A Unit test calls a specific method on a specific object and examines the result. When that method name changes or the object goes away, your test is broken. Functional tests are far less likely to break when your underlying implementation changes.
Unit testing has its place. I don't want to discourage you from ever writing unit tests. It just seems like Unit testing gets all of the attention, when Functional testing provides a far bigger bang for your buck. Again, ideally you would do both! But in the real world, most developers barely have time for one kind of automated test, let alone two. If that describes you, I would strongly encourage you to go with Functional testing.
CasperJS and PhantomJS
In web applications, you can't automatically test your front-end without using some sort of tool. There are a number of options out there, like Selenium, which has been around forever and has a nice GUI. If you want something lightweight and simple that you can run from the command line, CasperJS may be the right tool for you.
Casper runs on PhantomJS, a headless WebKit browser, and provides a full-featured API that lets you interact with a Phantom instance pointing at your application. So rather than setting up special test pages that run your Unit tests, you can test your actual application. For a virtuous developer like me, this is great.
Casper API
Casper is a command line utility that runs JavaScript code in the PhantomJS execution environment, but also provides the ability to execute JavaScript in the context of the Phantom WebKit browser, using the evaluate
method (more on this later).
To use Casper, you simply write some JavaScript, save it to a file, then run it from the command line like so: casperjs my-source.js
. If you will be running unit tests, you must include the test
command, like so: casperjs test my-test.js
. All of the examples in this post should be run with the test
command. If you want to follow along or run these tests locally, you can download the code from this GitHub repo.
Casper has a fantastic API full of convenience methods to help you interact with your phantasmic browser. There are two main modules that you can use, the casper module and the tester module. Methods in the tester module are only available when you run Casper with casperjs test my-test.js
.
Let's first look at what the main casper
module can do, then we'll look at tests in particular. To get started, let's open https://www.reddit.com/, print the page's title, and take a screenshot.
reddit-home.js
Simple enough. the start()
call opens the page and executes the callback when it's loaded. We then take a screenshot and save it to a PNG called reddit-home.png. Here's what it looks like:
Saving screenshots in this manner can be a great part of your functional tests, and can be hugely helpful as a debugging tool when writing your tests, helping you "see" what's going on in the invisible browser.
Casper Test API
Now let's take a look at Casper's test API. Let's open up the /r/programming subreddit, click the "New" link and confirm that we're on the right page and have the correct content.
REDDIT-NEW.JS
Here, after we click the "New" link, we wait for the url to change and a new page to load. Then we confirm that there are 25 links on the page, the Reddit default.
Casper's API is chock full of handy helper methodslike click()
and assertElementCount()
. Let's look at a more complicated method, fill()
. fill()
is a convenient way to fill out and (optionally) submit forms on the page. In this example, let's fill out the search box form and search for "javascript" within the /r/programming subreddit.
REDDIT-SEARCH.JS
Invading the DOM - casper.evaluate()
Sometimes, to really test something complicated, you need to jump into the DOM of the browser itself. Casper provides the ability to do just that with the [evaluate()](http://docs.casperjs.org/en/latest/modules/casper.html#evaluate)
method.
Here's an example where we will jump into the context of the r/programming page, click on upvote, confirm that the login modal appears, then click on the .close
backdrop and confirm that the modal disappears. The results are then returned to the casper environment.
REDDIT-MODAL.JS
An important thing to note when using casper.evaluate()
is that unlike almost any other interaction that occurs with the browser (click()
, .fill()
, etc.), evaluate
is synchronous. Whereas after calling fill
or 'sendKeys' you will need to wrap your next interaction in a casper.then()
callback, evaluate
happens instantly. This actually makes it easier to use than some of its asynchronous counterparts, but after a while you get used to asynchronous and have to remind yourself that evaluate is synchronous.
Thoughts on using Casper
Casper is probably not the best tool for someone who isn't familiar with JavaScript, and although it is concise and powerful, it has its pain points just like anything. Here are some of those and ideas that you might consider to help you write a clean, concise and maintainable codebase.
ASYNC AND .THEN()
Probably the most difficult part of using Casper is dealing with the asynchronous nature of pretty much everything. It is important to understand that then()
essentially adds navigation steps to a stack. So if you call click()
, you will need to wrap your follow-up code in a then()
callback in order to see the changes from your click event. I recommend reading helpful links like this StackOverflow question to try to wrap your head around this concept.
Code organization
Something I have found helpful in writing medium to large test suites is to make certain to organize code into separate files with specific purposes. This is easy, since the PhantomJS execution environment allows you to import/require other JS files using the CommonJS syntax. So if you have a utility file my-utils.js, you can import it and use it elsewhere.
MY-UTILS.JS
ELSEWHERE.JS
This is incredibly helpful, and allows you to organize your app into smaller pieces like you would in a "real" application. I have found it helpful to organize in these ways:
test.js - put your initial configuration here, and require() and run your tests here. Keep it as light as possible.
my-utils.js - have a common utility class for common actions and helper methods
navigation.js - separate your navigation (go to this page) from your tests, and make navigation reusable across tests. For instance,
require('./navigation').homePage()
takes you to, you guessed it, the home page.customer.js - test the "customer" section of your application. Keep this separate from login test and all of the other sections of your application you will be besting.
Basically, when you are writing your test suites, don't disregard all of your hard-won knowledge about good software engineering best practices because you are "just" writing tests. YAGNI. DRY. You know the drill.
Outro
If you've never really written unit or functional tests before, I hope you'll bite the bullet, take an hour and give Casper a shot. It has a great community, is actively maintained, and makes for a great, lightweight and code-centric solution to a difficult and important problem that gets addressed far too infrequently. If you're disillusioned with Unit Tests and TDD, don't throw the Functional Testing baby out with the proverbial bathwater. I think you'll find the ROI for Casper integration testing to be far higher than you ever expected.
If you'd like more information on Casper, check out these links:
The Supportive Weekly: A newsletter for people who want to deliver exceptional customer service.