Testing a Yesod+Angular JS Application
By John Lenz. December 17, 2013.
There are three kinds of testing that should be done: unit tests, mid-level tests, and end2end tests.
In unit testing, we test individual functions or individual components. In our Yesod+Angular app, there are two kinds of unit tests:
Mid Level Testing
In mid-level testing, we want to test combinations of code and functions together. In our Yesod+Angular application, there are two natural mid-level tests we want to carry out.
Test the Yesod Haskell server code. Here we want to test the Yesod handlers and routes are working properly; that form submissions work, routes return the correct data, and so forth. yesod-test is perfect for this. Remember here you are not testing any of the Angular code; you should be using
yesod-testto just make sure that the correct HTML is being served by the various handlers. (Note that
Most likely the unit and mid-level tests will be combined together and this is fine: a single test section of the cabal file builds an executable containing both the unit tests and mid-level tests using
yesod-test. Also a single instance of karma running both the unit and mid-level tests.
End To End Testing
Since webdriver is used in so many different ways, it is hard to get a picture of what it does. So here is a brief description of webdriver, specifically targeted at how we will be using it. First, webdriver defines a network API that allows browsers to be controlled. It is at the moment a W3 Working Draft. Scanning the W3 draft gives a good overview of what is possible using webdriver. The browser side of the webdriver API has been implemented in all the major browsers, sometimes directly and sometimes as an extension. For example, there is a Firefox extension that implements the network API and controls the browser. The client side of the webdriver API has at the moment only one implementation, an implementation written in Java as part of the selenium project and called selenium-webdriver.
Thus if you write your test code in Java, you can just call the selenium-webdriver methods and these then communicate with the browser to automate it (you see a lot of examples of this on the selenium site and elsewhere online). But the selenium folks realized that not everyone wants to write their test code in Java, so they broke out the webdriver code into a standalone application called selenium-server-standalone. They also defined another network API allowing programs to communicate with selenium-server-standalone. This is how e.g hs-webdriver works. You write test code in Haskell calling methods in hs-webdriver which in turn makes network requests to selenium-server-standalone. Then selenium-server-standalone uses the W3 Webdriver network API to send commands to the browsers. This is how all the various webdriver language implementations work, e.g. webdriver-js and webdriver-python. It seems somewhat weird to me. Why not have Haskell and the various other language modules talk to the browsers directly using the W3 webdriver API? Perhaps it is because the W3 webdriver API is very new and still in development. Also, it does allow the so-called selenium grid, where you run selenium-server-standalone on a separate computer and have many VMs with various browser and operating system version combinations all registered with selenium-server-standalone. Then your test code in Haskell can then just communicate with selenium-server-standalone and have the tests run on all the different browser and OS combinations.
WebDriver and Angular
The webdriver Haskell package implements almost all of the webdriver API and is straightforward to use. The webdriver API allows looking up elements on the page by css selectors, ids, and several other ways. But usually when using Angular you are not adding ids to all elements. You just specify some binding which Angular automatically keeps up to date (if you were using JQuery you would need all these elements to have ids so you could update them yourself). Also, elements generated by
With that, writing end to end tests using webdriver and webdriver-angular is quite easy. The only snag was that the webdriver package had the test code executing in the
WD monad, so I had to write a small amount of boilerplate to turn
WD monads into hspec
Examples and also to lift several hspec expectations like
shouldBe into the
WD monad. But once that was done, writing end to end tests were quite easy.