Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate from JUnit 4 to JUnit 5 #11839

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.sbt
Expand Up @@ -499,6 +499,8 @@ lazy val userProjects = Seq[ProjectReference](
PlayOpenIdProject,
PlaySpecs2Project,
PlayTestProject,
PlayTestJUnit4Project,
PlayTestJUnit5Project,
PlayExceptionsProject,
PlayFiltersHelpersProject,
StreamsProject,
Expand Down
@@ -1,12 +1,23 @@
<!--- Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com> -->

<!--- Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com> -->
Nezisi marked this conversation as resolved.
Show resolved Hide resolved

# Writing functional tests

Play provides a number of classes and convenience methods that assist with functional testing. Most of these can be found either in the [`play.test`](api/java/play/test/package-summary.html) package or in the [`Helpers`](api/java/play/test/Helpers.html) class.
Play provides a number of classes and convenience methods that assist with functional testing.

For scala code, helpers can be found in the [`play.test`](api/java/play/test/package-summary.html) package or in the [`Helpers`](api/java/play/test/Helpers.html) class.

Depending on framework, additional helpers are provided:

- [JUnit 5](https://junit.org/junit5/): [`play.test.junit5`](api/play/test/junit5/package-summary.html)
- [JUnit 4](https://junit.org/junit4/): [`play.test.junit4`](api/play/test/junit4/package-summary.html)

# [JUnit 5](https://junit.org/junit5/) test helpers

You can add these methods and classes by importing the following:

@[test-imports](code/javaguide/tests/FakeApplicationTest.java)
@[test-imports](code/javaguide/test/junit5/FakeApplicationTest.java)

## Creating `Application` instances for testing

Expand All @@ -16,62 +27,68 @@ Play frequently requires a running [`Application`](api/java/play/Application.htm
import static play.test.Helpers.*;
```

@[test-fakeapp](code/javaguide/tests/FakeApplicationTest.java)

## Injecting tests

If you're using Guice for [[dependency injection|JavaDependencyInjection]] then an `Application` for testing can be [[built directly|JavaTestingWithGuice]]. You can also inject any members of a test class that you might need. It's generally best practice to inject members only in functional tests and to manually create instances in unit tests.

@[test-injection](code/javaguide/tests/InjectionTest.java)
@[test-fakeapp](code/javaguide/test/junit5/FakeApplicationTest.java)

## Testing with an application

To run tests with an `Application`, you can do the following:

@[test-running-fakeapp](code/javaguide/tests/FakeApplicationTest.java)
@[test-running-fakeapp](code/javaguide/test/junit5/FakeApplicationTest.java)

You can also extend [`WithApplication`](api/java/play/test/WithApplication.html), this will automatically ensure that an application is started and stopped for each test method:
## Injecting tests

@[test-withapp](code/javaguide/tests/FunctionalTest.java)
If you're using Guice for [[dependency injection|JavaDependencyInjection]] then an `Application` for testing can be [[built directly|JavaTestingWithGuice]]. You can also inject any members of a test class that you might need. It's generally best practice to inject members only in functional tests and to manually create instances in unit tests.

@[test-injection](code/javaguide/test/junit5/InjectionTest.java)

## Testing with a Guice application

To run tests with an `Application` [[created by Guice|JavaTestingWithGuice]], you can do the following:

@[test-guiceapp](code/tests/guice/JavaGuiceApplicationBuilderTest.java)
@[test-guiceapp](code/javaguide/test/junit5/guice/JavaGuiceApplicationBuilderTest.java)

Note that there are different ways to customize the `Application` creation when using Guice to test.

# [JUnit 4](https://junit.org/junit4/) Test Helpers

## Testing with an application

To run JUnit 4 tests with an application, one can extend [`WithApplication`](api/play/test/junit4/WithApplication.html).

This will automatically ensure that an application is started and stopped for each test method:

@[test-withapp](code/javaguide/test/junit4/FunctionalTest.java)

## Testing a Controller Action through Routing

With a running application, you can retrieve an action reference from the path for a route and invoke it. This also allows you to use `RequestBuilder` which creates a fake request:

@[bad-route-import](code/javaguide/tests/FunctionalTest.java)
@[bad-route-import](code/javaguide/test/junit4/FunctionalTest.java)

@[bad-route](code/javaguide/tests/FunctionalTest.java)
@[bad-route](code/javaguide/test/junit4/FunctionalTest.java)

It is also possible to create the `RequestBuilder` using the reverse router directly and avoid hard-coding the router path:

@[good-route](code/javaguide/tests/FunctionalTest.java)
@[good-route](code/javaguide/test/junit4/FunctionalTest.java)

> **Note:** the reverse router is not executing the action, but instead only providing a `Call` with information that will be used to create the `RequestBuilder` and later invoke the the action itself using `Helpers.route(Application, RequestBuilder)`. That is why it is not necessary to pass a `Http.Request` when using the reverse router to create the `Http.RequestBuilder` in tests even if the action is receiving a `Http.Request` as a parameter.

## Testing with a server

Sometimes you want to test the real HTTP stack from within your test. You can do this by starting a test server:

@[test-server](code/javaguide/tests/FunctionalTest.java)
@[test-server](code/javaguide/test/junit4/FunctionalTest.java)

Just as there exists a `WithApplication` class, there is also a [`WithServer`](api/java/play/test/WithServer.html) which you can extend to automatically start and stop a [`TestServer`](api/java/play/test/TestServer.html) for your tests:
Just as there exists a `WithApplication` class, there is also a [`WithServer`](api/play/test/junit4/WithBrowser.html) which you can extend to automatically start and stop a [`TestServer`](api/play/test/TestServer.html) for your tests:

@[test-withserver](code/javaguide/tests/ServerFunctionalTest.java)
@[test-withserver](code/javaguide/test/junit4/ServerFunctionalTest.java)

## Testing with a browser

If you want to test your application from with a Web browser, you can use [Selenium WebDriver](https://github.com/seleniumhq/selenium). Play will start the WebDriver for you, and wrap it in the convenient API provided by [FluentLenium](https://github.com/FluentLenium/FluentLenium).

@[test-browser](code/javaguide/tests/FunctionalTest.java)
@[test-browser](code/javaguide/test/junit4/FunctionalTest.java)

And, of course there, is the [`WithBrowser`](api/java/play/test/WithBrowser.html) class to automatically open and close a browser for each test:
And, of course there, is the [`WithBrowser`](api/play/test/junit4/WithBrowser.html) class to automatically open and close a browser for each test:

@[test-withbrowser](code/javaguide/tests/BrowserFunctionalTest.java)
@[test-withbrowser](code/javaguide/test/junit4/BrowserFunctionalTest.java)
40 changes: 18 additions & 22 deletions documentation/manual/working/javaGuide/main/tests/JavaTest.md
Expand Up @@ -2,7 +2,9 @@

# Testing your application

Writing tests for your application can be an involved process. Play supports [JUnit](https://junit.org/junit5/) and provides helpers and application stubs to make testing your application as easy as possible.
Writing tests for your application can be an involved process.

Play supports [JUnit 5](https://junit.org/junit5/) and [JUnit 4](https://junit.org/junit4/) and provides helpers and application stubs to make testing your application as easy as possible.

## Overview

Expand All @@ -18,11 +20,13 @@ You can run tests from the sbt console.

Testing in Play is based on [sbt](https://www.scala-sbt.org/), and a full description is available in the [testing documentation](https://www.scala-sbt.org/release/docs/Testing.html).

## Using JUnit
## Using JUnit 5

The default way to test a Play application is with [JUnit 5](https://junit.org/junit5/).

The default way to test a Play application is with [JUnit](https://junit.org/junit5/).
To run JUnit 5 tests, the following SBT plugin is necessary: [SBT Jupiter Interface](https://github.com/sbt/sbt-jupiter-interface).

@[test-simple](code/javaguide/tests/SimpleTest.java)
@[test-simple](code/javaguide/test/junit5/SimpleTest.java)

> **Note:** A new process is forked each time `test` or `test-only` is run. The new process uses default JVM settings. Custom settings can be added to `build.sbt`. For example:

Expand All @@ -35,66 +39,58 @@ The default way to test a Play application is with [JUnit](https://junit.org/jun
> )
> ```

### Assertions & Matchers

Some developers prefer to write their assertions in a more fluent style than JUnit asserts. Popular libraries for other assertion styles are included for convenience.

[Hamcrest](http://hamcrest.org/JavaHamcrest/) matchers:

@[test-hamcrest](code/javaguide/tests/HamcrestTest.java)

### Mocks

Mocks are used to isolate unit tests against external dependencies. For example, if your class under test depends on an external data access class, you can mock this to provide controlled data and eliminate the need for an external data resource.

The [Mockito](https://github.com/mockito/mockito) library is a popular mocking framework for Java. To use it in your tests add a dependency on the `mockito-core` artifact to your `build.sbt` file. For example:

```scala
libraryDependencies += "org.mockito" % "mockito-core" % "2.10.0" % "test"
libraryDependencies += "org.mockito" % "mockito-core" % "5.4.0" % Test
```

You can find the current version number of `mockito-core` [here](https://mvnrepository.com/artifact/org.mockito/mockito-core).

Using Mockito, you can mock classes or interfaces like so:

@[test-mockito-import](code/javaguide/tests/MockitoTest.java)
@[test-mockito-import](code/javaguide/test/junit5/MockitoTest.java)

@[test-mockito](code/javaguide/tests/MockitoTest.java)
@[test-mockito](code/javaguide/test/junit5/MockitoTest.java)

## Unit testing models

Let's assume we have the following data model:

@[test-model](code/javaguide/tests/ModelTest.java)
@[test-model](code/javaguide/test/junit5/ModelTest.java)

Some data access libraries such as [Ebean](https://ebean.io/) allow you to put data access logic directly in your model classes using static methods. This can make mocking a data dependency tricky.

A common approach for testability is to keep the models isolated from the database and as much logic as possible, and abstract database access behind a repository interface.

@[test-model-repository](code/javaguide/tests/ModelTest.java)
@[test-model-repository](code/javaguide/test/junit5/ModelTest.java)

Then use a service that contains your repository to interact with your models:

@[test-model-service](code/javaguide/tests/ModelTest.java)
@[test-model-service](code/javaguide/test/junit5/ModelTest.java)

In this way, the `UserService.isAdmin` method can be tested by mocking the `UserRepository` dependency:

@[test-model-test](code/javaguide/tests/ModelTest.java)
@[test-model-test](code/javaguide/test/junit5/ModelTest.java)

## Unit testing controllers

You can test your controllers using Play's [test helpers](api/java/play/test/Helpers.html) to extract useful properties.

@[test-controller-test](code/javaguide/tests/ControllerTest.java)
@[test-controller-test](code/javaguide/test/junit5/ControllerTest.java)

## Unit testing view templates

As a template is a just a method, you can execute it from a test and check the result:

@[test-template](code/javaguide/tests/ControllerTest.java)
@[test-template](code/javaguide/test/junit5/ControllerTest.java)

## Unit testing with Messages

If you need a `play.i18n.MessagesApi` instance for unit testing, you can use [`play.test.Helpers.stubMessagesApi()`](api/java/play/test/Helpers.html#stubMessagesApi\(java.util.Map,play.i18n.Langs\)) to provide one:

@[test-messages](code/javaguide/tests/MessagesTest.java)
@[test-messages](code/javaguide/test/junit5/MessagesTest.java)
Expand Up @@ -26,19 +26,19 @@ Play provides some helper utilities for mocking a web service in tests, making t

As an example, let's say you've written a GitHub client, and you want to test it. The client is very simple, it just allows you to look up the names of the public repositories:

@[client](code/javaguide/tests/GitHubClient.java)
@[client](code/javaguide/test/junit5/GitHubClient.java)

Note that it takes the GitHub API base URL as a parameter - we'll override this in our tests so that we can point it to our mock server.

To test this, we want an embedded Play server that will implement this endpoint. We can do that by [[Creating an embedded server|JavaEmbeddingPlay]] with the [[Routing DSL|JavaRoutingDsl]]:

@[mock-service](code/javaguide/tests/JavaTestingWebServiceClients.java)
@[mock-service](code/javaguide/test/junit5/JavaTestingWebServiceClients.java)

Our server is now running on a random port, that we can access through the `httpPort` method. We could build the base URL to pass to the `GitHubClient` using this, however Play has an even simpler mechanism. The [`WSTestClient`](api/java/play/test/WSTestClient.html) class provides a `newClient` method that takes in a port number. When requests are made using the client to relative URLs, eg to `/repositories`, this client will send that request to localhost on the passed in port. This means we can set a base URL on the `GitHubClient` to `""`. It also means if the client returns resources with URL links to other resources that the client then uses to make further requests, we can just ensure those a relative URLs and use them as is.

So now we can create a server, WS client and `GitHubClient` in a `@Before` annotated method, and shut them down in an `@After` annotated method, and then we can test the client in our tests:

@[content](code/javaguide/tests/GitHubClientTest.java)
@[content](code/javaguide/test/junit5/GitHubClientTest.java)

### Returning files

Expand Down Expand Up @@ -84,6 +84,6 @@ You may decide to modify it to suit your testing needs, for example, if your Git

Now, modify the router to serve this resource:

@[send-resource](code/javaguide/tests/JavaTestingWebServiceClients.java)
@[send-resource](code/javaguide/test/junit5/JavaTestingWebServiceClients.java)

Note that Play will automatically set a content type of `application/json` due to the filename's extension of `.json`.