Writing applicative tests

When testing a web application, we quickly come upon the problem of setting up a rather complete environment. This environment is meant to contain enough information needed by business workflows. Such unit tests reach the limits of atomic tests and thus can be considered applicative.

Such an environment can be complex because, most of the time, it involves a database or an application context with components such as caching. This task can be cumbersome in other frameworks because they either don't provide the whole stack, like Play! Framework 2 does, or they require several actions (new dependencies, annotations, project-specific configuration, dedicated test runner, and so on) to be implemented.

In Play! 2, applicative tests are handled by the framework itself through the definition of a bunch of helpers and mock-ups.

The key point will be the Application class, which is responsible for setting up the context of the web application. Indeed, an Application instance is created at the very start of a Play! 2 application. It will also configure third-party tools based on the application.conf file content. In a sense, it's able to simulate everything, even the HTTP server itself. Actually, it won't accept HTTP requests but it will accept simulated ones.

Examples of what will be configured are the database connection provider, the cache system, the routing component, and so on.

What we end up with, with this Application in hand, is the ability to test our Controllers or templates, or even the routes themselves. However, what we won't be able to do at this stage is test remote functionalities such as HTTP requests served by a real web server.

As said previously, Play! Framework 2 provides a good set of tools in order to start or simulate such an application programmatically in our tests. This is done using the running helper.

We'll create an example in a new file called test/applicative/LoginSpec.scala that will contain some tests about login processes.

The following screenshot shows what it might look like with two sample tests:

Writing applicative tests

The structure of the test is exactly the same as the atomic one, however we see the appearance of a wrapper around our tests called running.

This method is able to start its first parameter and run the test block that is given as the second parameter.

In this case, the first parameter is a mock-up of our Application; for this parameter, a dedicated FakeApplication case class is available in the play.api.test package. By running this fake application, we'll have the opportunity to test almost everything that defines it, so we can test the rendering of a template or a controller's result.

The first test we've defined in the previous example is checking that the login action in the Application controller will return an OK result, that is, a response with a 200 HTTP status.

For that check, we used a matcher that Play! 2 has defined in the play.api.test.Helpers object (which, by the way, is the object that defines the running function as well). What this status matcher does is retrieve the status of the result (set using the ok method in our action) and check it against the 200 constant.

Something to note before switching to the next test is the usage of getWrappedResult on the result of the action. We did this because we're testing a Java action using Scala matchers. These matchers are thus expecting Results from the Scala world, given that Java Results is just a wrapper around the Scala version.

The second test is playing a different game. It's checking the validity of a template by simply invoking it. This has the advantage of skipping the business logic defined in an action to test specific use cases.

The template we're testing, the login one, expects to return an HTML result content that contains a form tag.

These checks are straightforward; they are performed using the dedicated matchers contentType and contentAsString from Play! 2. Where the former is checking the encoding header, the latter is reading the body as a String. Also, we can use the contain matcher from specs2 to check if a string is part of another.

So far so good; now we need to run them. For this we keep consistent by running test in the console, due to which both the ComparisonTests and LoginSpec tests classes will run.

Here is the result we would get:

Writing applicative tests

Oh my! Errors again!

Don't give up; since we saw the application running and have logged in thousands of times in the previous chapters, there must have been a mistake somewhere in the test or in the architecture.

Before debugging, we're going to remove the noise, which produced our first successful test, from the ComparisonTests class. To do this, SBT has a special command that enables us to target a specific test class to run rather than launching all test suites. This command is test-only and we can use it as shown in the following screenshot:

Writing applicative tests

Tip

Since test-only is used when debugging a specific behavior that is exposed in a test, this latter test will probably be run many times until the fix is found. In such a case, SBT has a special trick called continuous command . Simply prefixing a command with a tilde (~) will enable this command to run whenever a file has been touched (saved) in the sources. In this case, we can use ~test-only.

We simply called the command by specifying the path to the test class that is to be run. In this case, we want a specific class to be run, and SBT (onto which the Play! console is built) is aware of the classpath, so you can use tab to auto-complete up to the complete qualified name of the test class.

At least the whole message fits in the console window now and what it says is that an HTTP context is required to run the tests.

Since the Application class we ran is everything but an HTTP stack and since we're defining applicative tests, we can't try to run an HTTP server at this stage and so we cannot have such HTTP context.

Furthermore, a login page should have nothing to do with the server; it should only show a login form and that's all. We must have done something wrong in our code, and where to look is provided by the stacktrace (as usual).

Note

I took a shortcut here. It is possible to simulate an HTTP context as well, and we'll do it next using the router. The fact is that this page shouldn't require it.

Looking at the stacktrace, we understand that line 57 of our compiled main template expects a session – which is a cookie in Play! and thus it's part of an HTTP context.

Why is the main template involved here? It's because the login template is using it to set the regular layout (the HTML boilerplates such as HTML tags and so on).

However, to find the problem even more easily than checking the template itself, for which we don't have the line number where it has failed, we can go into the compiled file itself instead.

The class file that has been created based on the template is apparently named main.template.scala, so we can simply try to find it by searching the target folder, but let me give the path and show its content directly.

This file is located under the folder /target/scala-2.9.1/src_managed/main/views/html/main.template.scala.

Tip

If you're using Sublime Text, for instance, just hit CTRL + P and type the name of the file to access it.

The file looks like the following screenshot:

Writing applicative tests

Interesting! A template has been compiled into a Scala object and what it seems to be doing is building a bunch of String values (multiline String values are possible in Scala using triple double quotes rather than single ones).

These String values that are essentially the HTML code from the template are interleaved with Scala calls to a _display_ function, which takes the Scala code to be executed and dumps its result in the output.

The line that was erroneous in our tests was the 57th one, and what we can see in this line is a call to Option(session().get("email")).

That's true! We're in the main template, which is the base of all high-level templates and that uses an active session. This doesn't make much sense, because if we consider the login page, this one mustn't rely on the existence of a session since it's the entry point that could create it.

So, we know our architectural mistake and we also understand why it never failed—because we ran it in a real server that can create an HTTP cookie!

Although we know what our error is, we still have to find it in our original main.scala.html file, which can become a painful task when huge templates are involved. However, Play! Framework 2 has foreseen this problem and tells us which is the line number in the template file as well. Indeed, back to our main.template.scala file at line 57, right before the call to Option, we see the /*28.10*/ comment. This comment refers to the line in the template that has generated the following Scala code.

Now we go to our main.scala.html file at line 28 and character 10.

Writing applicative tests

Oh yes, the check on the connected user is already done here; this means that every page that will rely on this template to set its HTML boilerplate will involve a check in the cookie, irrespective of whether it makes sense or not.

Moreover, if we take a deeper look at this template, it includes way too much information, such as the scripts declaration, the initial creation of the Twitter JavaScript tool, and even the tweets panel that is declared there.

Since it's a common mistake, there is a common solution! The solution is to create two levels of main templates, one for the HTML boilerplate (such as the DOCTYPE declaration and common scripts) and another that includes everything needed for the application to run—the second will rely on the first one.

Having created them (refer to the following list), we will have to go through each dependent template and decide whether it is part of the application business (which requires a logged-in user) or not.

To do so, what has to be done is to create another template that we'll name mainExtended. This new template will hold the following:

  • A call to the main template (which we might rename to something like "bootstrap", for instance)
  • The check in the cookie for a logged-in user
  • The scripts that are only relevant while the application business is involved (the chatrum itself)

Finally, the Twitter part will be moved to the dashboard/index.scala.html template, which is the only place where it'll be used.

This means that the main.scala.html file should be quite empty now, as shown in the following screenshot:

Writing applicative tests

The main template now contains only the common resources that are needed across all other templates, which are the jQuery library, the JavaScript reverse routers, and the stylesheets.

The mainExtended template is almost similar to what we cut from the main template. Take a look at the following screenshot:

Writing applicative tests

It simply calls the main template by giving its own title, and the second parameter is only another wrapper over its own content variable. The wrapped code involves the user login information in the session and will insert the JavaScript at the end of the body (a common technique to accelerate the loading time of a web interface).

Up to now, the Twitter integration hasn't been restored. Actually, since we're pretty sure that this tool will only be used in the dashboard, we can delegate its loading to the dashboard/index.scala.html template as follows:

Writing applicative tests

We will add the tweets container, the script loading instruction, and finally the JavaScript instance of the Twitter tool when the whole document is ready.

Note

You might be wondering why we left all the JS libraries in mainExtended, and you would be right because we should have dispatched these libraries as well. We left them there for illustration purposes.

In this template, we can also notice that we changed the call from main to mainExtended. Indeed, it's our last step. Review all the templates that are using the main template and check whether they should or should not use the extended one. Of course, login.scala.html should remain unchanged.

Having done that, we've cleaned our application up a bit and we're now ready for a second check on the applicative side by running the test-only command again.

Writing applicative tests

Here we are! Now our login page is quite done, but what about the login validation in the database and so on? Right!

It'd be worth it now to spend some time on this as well, checking, for instance, whether an unknown user has been redirected to the login page again or not. For this there is the enter action in Application that we've to test; the problem with this action is that it requires some data in its body. So, it's not like login that we were able to call directly.

In this case, we really need a request , at least a mock-up, that is a FakeRequest instance. This mock-up can mimic everything a real request can do, so it'll enable us to put some data in its body (if it's a POST or PUT request). Then we'll have two ways to use it:

  • By calling the action (enter) with it
  • By using the router to send it to the target URL (/enter)

Okay, we should create such a FakeRequest matching the requirements of the enter action, which is the only URL-encoded email parameter in the body. Using this information, the action will check in the database if a User exists with this e-mail.

The following screenshot shows two examples, one for each option:

Writing applicative tests

Indeed, they are both identical; only the way to call the action is different, but they are equal. Before tackling these lines, we'll review what has been done.

First, we created a FakeRequest (from the Java test API) and updated its body with what the enter action expected, that is, the email parameter. This email was then encoded as a form URL, since the enter action is dealing with such content only. Then, this request was served using the callAction Java test helper. This helper requires an action to be called with a request. Exactly what we want to do! So, we used it by giving it the enter action reference available under the package controllers.routes.ref that gives access to the action instance that Play! 2 will generate based on the static method defined in the Application controller. The second parameter is simply our request.

Using callAction is the key point here, since it'll simulate the action on the request. However, note that we're still using the Java version of the test helpers to access the action instance and call it. Thus, the action will have access to the request's body to fetch parameters and so on.

In this case, we're trying to enter the application using a given e-mail that doesn't exist in the database, so the enter action should redirect to the login page.

This redirect information can be retrieved using the redirectLocation helper by doing two things at once, namely checking if the status is one of the semantically equivalent ones (such as MOVED PERMANENTLY or TEMPORARY REDIRECT) and then checking if it returns the Location header.

Since either the status can be wrong or the header can be absent, the return type is Option. Given that we expect it to exist, we can simply check that header to be an instance of Some which wraps the "/login" String.

In this test, we didn't use any HTTP stack (not even a fake one). For such use cases we can use another helper to call the action, that is, the routeAndCall one. Its usage can be seen in the second example shown in the previous screenshot. The preparation and checks are exactly the same but the call itself is different. However, you'll probably only use the second version that is less verbose, but it's important to know that no magic is involved. That's what the first version is showing. All actions are compiled into dedicated objects that will be available for testing purposes (in this case).

The really interesting thing to note so far is that we used the database to check the user's existence without (re)configuring or even mentioning it. It worked because we ran a fake version of our application, and also because our application is using a database.

On the other hand, we're in the easiest situation, where the development database is the same as the tests one. In this case, it's an in-memory database that is started with the application. Most of the time, we have a dedicated test database for efficiency or to reduce resource consumption while testing or, especially, to target different vendors (MySQL, SQLite, Oracle, PostgreSQL, and so on). This can be achieved by giving an extra parameter to our FakeApplication, as shown in the following screenshot:

Writing applicative tests

But wait; if it simulates an HTTP stack, we could check for a valid user to connect and that his/her e-mail will be stored in the session! How about the test shown in the following screenshot:

Writing applicative tests

What's being done here? We begin by creating an instance of a User with its mandatory Address reference, then they are both made persistent using save, and finally we create a fake request targeting the enter action. Note that we update the request with the new user's e-mail.

After having asked it to be routed and called, we must check what the result of this action should be. The enter action will check that the e-mail corresponds to an existing User. If so, it'll store the e-mail in the session and redirect the page to the dashboard index page. The last three lines of the test check these things by doing the following:

  1. Checking that the session is not null using the helper session that extracts the session object from the cookie.
  2. Checking that the session has an entry named email. When retrieving its value (instance of Option type) from the session, we check that it should be an instance of the Some type. Where such instance extends the Option type by providing a content (the e-mail in this case).
  3. Checking that the header Location and the status define a redirection to the index page of the dashboard.

That's it; we're now able to test anything in our application, right from the top layers to the bottom ones. Plenty of other test helpers are also provided by the framework, but it'd be overkill to present them all here. The best would be to check Scala/Javadoc of the Helpers classes when needed.

We've just said that the top layers are testable; it's definitively true for the server side but not for its exported features that compose of the workflows enabled by a web application, such as the operations that a service is exposing or the UI that it is presenting.

For this, we'll have to write different kinds of tests that have yet another more complicated and more complete set of needs. This is the topic of the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset