First and foremost, Litmus provides you a very fluent api for your functional testing needs. You’ll be able to test the rendered html and text that comes out of controller methods, the content of cookies, existence, non-existence and content of validation messages, renderArgs and more.
You’ll also get a very convenient way to call your controller methods. Instead of new-ing a HashMap<String, String>()
, putting all request params in it and lose precious lines of code, you can simply do new Request("/some/path").with("param1", "value").post()
.
Next to that, Litmus gives you a parent ValidationTest you can extend to test the validation annotations on your models. What excuse do you have left not to tests these things when you can (and only have to) write assertThat("firstName").isRequired()
.
Under the hood, litmus uses the following libraries
- Fest-assert’s fluent assertThat() API.
assertThat(actual).isEqualTo(expected)
feels more natural thanassertEquals(expected, actual)
. A big plus is that this syntax allows you to chain asserts. - JSoup’s html parser, to enable you to assert things on the rendered html.
If this documentation page is not enough, there’s a litmus-samples repository on github.
Go and check that out for all possible examples on how to use litmus.
First off, you need to declare litmus as a project dependency in your dependencies.yml
.
After that, you can just start to write your first litmus tests. To do so, you can extend either litmus.functional.FunctionalTest
and litmus.unit.ValidationTest
.
These two parent test classes have a very specific purpose.
- FunctionalTest is used to call action methods and assert things on the response such as cookies, renderArgs, the response body itself, etc.
- ValidationTest is a small utility test class to help you in writing validation tests.
Both are subclasses of the regular Play! test classes, so you’ll be able to run it from the play testrunner ui, or by running play auto-test
.
litmus.functional.FunctionalTest
gives you the following:
- all fest’s
assertThat(xxx)
methods are available in a litmus functional test (though not as static methods). That way, you can write things likeassertThat(aString).isNotEmpty();
- next to that, you get a number of custom Play-specific Assert classes, so you can write asserts on the response itself (contentType, http status code, …), cookies, flash variables, header parameters, the html in the response body, the render arguments you passed to the view in your controller, etc.
- utility methods to easily call action methods on your controllers.
In general, when you’re writing a functional test, you do the following:
- build a Request object
- call a http method on this Request, which results in a Response object, your ‘actual’.
- assert that the actual response is conform to what you expect.
Request request = new Request("/path/as/defined/in/your/routes/file");
Response response = request.get(); // or request.post(), or ...
assertThat(response).isOk();
The Response object is a Litmus class that wraps @play.mvc.Http.Response and adds some functionality to retrieve the renderArgs and stuff.
the ResponseAssert
class is responsible for asserting that the actual response you got from sending your request is conform to what you expect:
Asserts on http status codes
assertThat(response).isStatus(200);
assertThat(response).isOk();
assertThat(response).isNotFound();
assertThat(response).isForbidden();
...
assertThat(response).isSuccess(); // status code is 200-299
assertThat(response).isError(); // status code is 400-599
assertThat(response).isRedirect();// status code is 300-399
// or you can also assert the location header:
assertThat(response).isRedirectTo("/another/path");
Asserts on headers
assertThat(response).hasHeader("location");
assertThat(response).hasNoHeader("location");
Asserts on Flash Parameters
assertThat(response).hasFlashParam("aKey");
// or if you want to assert its content too:
assertThat(response).hasFlashParam("aKey", "aValue");
assertThat(response).hasNoFlashParam("aKey");
Asserts on cookies
assertThat(response).hasCookie("cookieName");
assertThat(response).hasCookieValue("cookieName", "cookieValue");
assertThat(response).hasNoCookie("cookieName");
Asserts on renderArgs
assertThat(response).hasRenderArg("name");
assertThat(response).hasRenderArgValue("name", anObject);
assertThat(response).hasNoRenderArg("name");
Asserts on content type
assertThat(response).hasContentType("text/plain");
...
// there are also some shortcut methods
assertThat(response).isHtml(); // text/html
assertThat(response).isXml(); // text/xml
assertThat(response).isJson(); // application/json
assertThat(response).isText(); // text/plain
Asserts on response encoding
assertThat(response).isEncoded("utf-8");
// or shorter:
assertThat(response).isUtf8();
Assert on validation errors
import static litmus.unit.validation.BuiltInValidation.REQUIRED;
...
assertThat(response).hasValidationError("firstName", REQUIRED);
First, you need to get the html from the response. You can do this either by calling .getHtml()
on a response object, or by using a shortcut method on FunctionalTest itself.
Option 1 (which is to be preferred when you want fine-grained control over your request)
Html html = new Request("/a/path")
.with("paramName", "paramValue")
.post()
.getHtml();
Option 2 (much shorter when you’re just GETting html)
Html html = getHtml("/a/path")
Next, you’ll want to assert that the resulting Html is conform to what you expect. You have several methods available:
Title assertions:
assertThat(html).hasTitle("pageTitle");
assertThat(html).titleContains("geTitl");
assertThat(html).titleMatches("aRegex");
Html content assertions:
assertThat(html).containsElement("#myDiv");
assertThat(html).doesNotContain("#myDiv");
assertThat(html).contains("<div id='myDiv'>This is my div!</div>");
Litmus uses JSoup behind the scenes to do the html parsing and . See the jsoup documentation for more info on the supported selector syntax.
For more fine-grained asserts, you can also call .getElement("css selector")
on an Html object. This returns a jsoup org.jsoup.nodes.Element
object for which Litmus has an HtmlElementAssert class.
This means you can do things like:
@Test
public void testHtmlContent(){
Element actualDiv = getHtml("/a/path").selectSingle("#aDivId");
...
assertThat(actualDiv).containsText("this is the div text");
assertThat(actualDiv).containsTextIgnoringCase("tHis iS ThE DiV tExT");
assertThat(actualDiv).hasClass("aCssClass");
assertThat(actualDiv).hasAttribute("title");
assertThat(actualDiv).hasAttributeValue("title", "myTitle");
assertThat(actualDiv).containsMessage("a.message.key");
}
ValidationTest is a superclass you can use to easily test validation annotations on your models. The idea is the following: for each test method in
the test class, you start from a valid object, then change something, validate the object and assert that a validation error has occured (or not).
To use it, you just
- extend it, using the class under test as a generic type
- implement the abstract
valid()
method. This method should return an instance of the object under test that is valid.
public class PersonValidationTest extends ValidationTest<Person> {
@Override
protected Person valid() {
return new Person("Ben", "Verbeken");
}
}
After that, you can write your test methods like this:
import static litmus.unit.validation.BuiltInValidation.REQUIRED;
...
@Test
public void firstNameIsRequired() {
assertThat("firstName").withValue("").isInvalid();
}
You can use another method, if you want to chain asserts:
@Test
public void firstNameIsRequired() {
assertThat("firstName").shouldNotBe("").shouldNotBe(null);
}
If you want to check for a specific validation error type, you can use the BuiltInValidation enum and do:
import static litmus.unit.validation.BuiltInValidation.REQUIRED;
...
@Test
public void firstNameIsRequired() {
assertThat("firstName").withValue("").isInvalidBecause(REQUIRED);
}
Finally, you can of course also test your custom validation annotations, using
@Test
public void myCustomValidation() {
assertThat(fieldName).withValue(x).isInvalidBecause("custom.msg.key");
}
ValidationTest itself also contains a test method, called validObjectShouldValidate()
, that tests whether valid()
really returns a valid object. If your implementation of valid()
does not return a valid object (according to the annotations you put on its fields), this test will fail.
Instead of Selenium 1, litmus provides you with Selenium WebDriver support for you UI testing needs.
Basically, all you have to do is create a subclass of litmus.WebDriverTest
and write the test and page objects.
Litmus provides you with a WebElementAssert, so you can write:
@Test
public void someTest(){
SayHelloPage page = new HelloWorldPage()
.open()
.enterName("World");
assertThat(page.message).containsText("Hello");
}
The containsText()
method is part of @WebElementAssert@’s API.
Please refer to the examples for more information.
Currently, there are two issues with litmus WebDriver tests:
- only firefox is supported
- each test quits the browser, which makes running multiple WebDriver tests slooow.
In order to be able to run WebDriver tests, play needs an execution pool of at least 2 threads. To configure you app this way,
add %test.play.pool=2
to your application.conf file.
litmus.WebDriverTest
checks this setting your play configuration before running.
When using litmus, you get an alternative test runner for free: point your browser to http://localhost:9000/@tests to see it.
Litmus’ test runner is obviously based on the original play test runner app, but with a couple of differences:
- You can categorize your tests
- There’s a run button per test class, and per category. Handy if you just want to run a single test.
Note: you can disable the alternative test runner by adding litmus.runner=false
to your application.conf file.
To categorize tests, just put a litmus.Category
annotation on a test or a superclass, like so:
@Category("MyCategory")
public class SomeTest{
...
}
You can also give these tests a priority (an int). This priority will determine how high the category will be shown in the litmus test runner. The lower the number, the higher the priority.
Subclasses of litmus.UnitTest
, litmus.FunctionalTest
and litmus.WebDriverTest
will get categorized by default, with respective priorities of 10000, 20000 and 30000.