Chapter 2. Persisting Data and Testing

In this chapter, you will see how you can write executable specifications for your web service and how Play can integrate mainstream data persistence technologies like RDMSes or document stores. More precisely, you will see how to perform the following:

  • Write and run unit tests
  • Simulate HTTP requests and inspect returned HTTP responses
  • Persist data using an RDBMS
  • Use an in-memory database for development

Testing your web service

The architecture of your web service is depicted in the following diagram:

Testing your web service

This section presents the testing libraries that are integrated with Play and the testing infrastructure provided by Play to test the HTTP layer of your web service.

Writing and running tests

As Play projects are just sbt projects by default, you can add tests to your Play project just as you would do for any other sbt project, except that the root directory for test sources is not src/test/scala/ but simply test/.

sbt provides a mechanism to integrate testing libraries so that their tests can be run from the build system. The testing component of Play integrates two testing libraries out of the box: specs2 for Scala tests and JUnit for Java tests. Play projects automatically depend on the Play testing component, so you don't need to add this dependency in your build.sbt file. Obviously, you are free to use any other testing library supported by sbt—just follow their usage instructions.

Note

Though this book only presents the specs2 integration, it is worth noting that for Scala developers, efforts have been made to provide a seamless integration of the ScalaTest library. It takes the form of an additional library named ScalaTest + Play. Refer to the official documentation for more information.

The most common form of specs2 tests is a class extending org.specs2.mutable.Specification as in the following test/models/ShopSpec.scala file:

import org.specs2.mutable.Specification
class ShopSpec extends Specification {
  "A Shop" should {
    "add items" in {
      failure
    }
  }
}

JUnit tests are just methods of a class that does not have an argument constructor. These methods must return void and be annotated with @Test:

import org.junit.Test;
import static org.junit.Assert.fail;
public class ShopTest {
  @Test
  public void addItem() {
    fail();
  }
}

Refer to the documentation of specs2 or JUnit for more information on how to write tests with these libraries. Note that Java Play projects also integrate fest-assert, a library to write fluent assertions.

You can run your tests by running the test sbt command. It should compile your project and tests, run them, and show a nice test report:

[info] ShopSpec
[info] A Shop should
[info] x add items
[error]    failure (ShopSpec.scala:6)
[info] Total for specification ShopSpec
[info] Finished in 14 ms
[info] 1 example, 1 failure, 0 error
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0

As an example, here is a specification that checks whether an item can be inserted in the shop:

"add items" in {
  Shop.create("Play Framework Essentials", 42) must beSome[Item].which {
    item => item.name == "Play Framework Essentials" && item.price == 42
  }
}

The Java equivalent code is as follows:

@Test
public void addItem() {
  Item item = Shop.create("Play Framework Essentials", 42.0);
  assertNotNull(item);
  assertEquals("Play Framework Essentials", item.name);
  assertEquals(new Double(42.0), item.price);
}

Testing the HTTP layer

So far, I've explained which existing testing technologies are shipped with Play, but the framework also offers a testing infrastructure, making it easier to build HTTP requests and to read HTTP responses so that you can effectively test your HTTP layer by performing HTTP requests and checking whether their result satisfies a given specification.

In order to build such HTTP requests, you need to know how you can generate URLs to call your actions and how Play applications are loaded and started.

Using the reverse router to generate URLs

The first step to build an HTTP request consists of defining the HTTP method and the resource URL to use. For instance, to make an HTTP request on the Items.list action, you will use the GET method and the /items URL, according to your routes file. You could just hard code these values in your test specifications, but that would be a very bad idea for at least two reasons.

First, the mapping between URL shapes and actions is already defined in the routes file. By hard coding the URL and method in your test code, you would be duplicating the information of the routes file, which is bad because you would have to update at two places (in the routes file and in your test code) if you ever wanted to change the mapping of this action.

Second, URLs should be percent encoded, which is a tedious task that you could be tempted to disregard. In the case of the /items URL, this would not be a problem because alphanumeric characters don't need to be encoded.

Hopefully, Play solves both problems by providing a reverse router. While the router dispatches an HTTP request to its corresponding action, the reverse router does the opposite job—it generates the URL and method corresponding to an action call. For instance, you can get the URL and method corresponding to the controllers.Items.list action call as follows:

controllers.routes.Items.list()

This expression returns a Call object containing two fields, url and method, which in our case, are equal to "/items" and "GET", respectively.

It also works with routes that take parameters. The controllers.routes.Items.details(42) expression returns an object with url and method members equal to "/items/42" and "GET", respectively.

The reverse router is automatically generated by the Play sbt plugin each time you change your routes file and guarantees that the URLs you generate are consistent with the routing process and properly encoded. For each action referenced in a route definition, the reverse router generates an object with the same name as the controller in a routes subpackage and contains a method with the same name and signature as in the route definition.

Running a fake Play application

Once you get a Call object that defines the URL and method to use to call the action you want to test, the next step consists of asking the framework to run the routing logic to effectively call the corresponding action. This process is handled by the router, and because it can be overridden by your application, it requires that you start the application. Actually, when you use the run sbt command, Play manages to load and start your application but this is not the case when running tests so you have to manually start your application. Note that manually achieving this also gives you more control over the process. The Scala testing API defines a specs2 scope named play.api.test.WithApplication, which starts the application before running the test content and stops it after the test execution. You can use it as follows:

import play.api.test.WithApplication
"a test specification" in new WithApplication {
  // some code relying on a running application
}

The Java API defines the following equivalent static methods:

import play.test.Helpers;
Helpers.running(Helpers.fakeApplication(), () -> {
  // some code relying on a running application
});

The running helper takes an application as parameter and starts it before evaluating its second parameter.

You now are ready to write specifications against the HTTP layer!

Effectively writing HTTP tests

In Scala, I recommend your test classes to extend play.api.test.PlaySpecification instead of org.specs2.mutable.Specification. You will get helper methods to call your actions and inspect their result (for example, in a test/controllers/ItemsSpec.scala file):

package controllers
import play.api.test.{PlaySpecification, FakeRequest, WithApplication}
import play.api.libs.json.Json

class ItemsSpec extends PlaySpecification {
  "Items controller" should {
    "list items" in new WithApplication {
      route(FakeRequest(controllers.routes.Items.list())) match {
        case Some(response) => status(response) must equalTo (OK) contentAsJson(response) must equalTo (Json.arr())
        case None => failure
      }
    }
  }
}

The route method calls the Items.list action using a fake HTTP request and returns its response. Note that the fake request is built using the reverse router. Then, if the routing process succeeds, the status method extracts the response status code and the contentAsJson method reads the response content and parses it as a JSON value. Note that all HTTP standard values (status codes and header names) are also brought by the PlaySpecification trait, allowing us to just write OK instead of 200 to describe a successful status code. In addition to the methods illustrated in the previous code, PlaySpecification also defines helper methods to inspect response headers and in particular cookies and content type.

Note

You might wonder why we are using helper functions such as status to manipulate the values returned by action calls, instead of directly invoking methods on them. That's because, as it will be explained further in the book, action invocation is asynchronous and returns a value of type Future[Result]. The helpers we use in the tests are actually blocking; they wait for the completion of the result. Though blocking threads is not recommended, it is convenient when writing tests. Also, note that two action calls in a test will be concurrent unless you wait for the completion of the first action. You can do this by calling the await helper on your first action call.

For Java users, Play provides a class called Helpers with convenient static fields describing HTTP standard values (status codes and header names) and static methods to call controller actions, build requests, and inspect responses:

import org.junit.Test;
import play.mvc.Result;
import static play.test.Helpers.*;
import static org.fest.assertions.Assertions.*;

public class ItemsTest {
  @Test
  public void listItems() {
    running(fakeApplication(), () -> {
      Result response = route(fakeRequest(controllers.routes.Items.list()));
      assertThat(status(response)).isEqualTo(OK);
      assertThat(contentAsString(response)).isEqualTo("[]");
   });
  }
}

I recommend that you import all the helpers using a wildcard static import as shown in the preceding code. The route helper dispatches a given request and returns its response. Then, the status helper method extracts the response status code and the contentAsString method extracts the response body.

Refer to the API documentation of play.api.test.PlaySpecification (play.test.Helpers in Java) for an exhaustive list of supported features.

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

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