springtester
The springtester
module is used in conjunction with the spring module. Internally, it uses the Mockito library and provides additional classes for injecting mocks/stubs when writing tests for your Play! application.
Changes
Version 2.0:
- Resolve issue where circular dependency can cause mocks being registered to be null’ed out
- Upgraded the Spring module dependency from 1.0.1 to 1.0.3
- Upgraded the Mockito library dependency from 1.9.0 to 1.9.5
Version 1.6:
- Fix which allows mocks that are registered in the test to be resolved in production code using Spring.getBeanOfType(Class). Previously when mocks were injected into Spring’s application context, this would have prevented using Spring.getBeanOfType(class) as it would have returned null (i.e. the only way of resolving dependencies was by using @Resource in classes that were annotated with @Component)
Version 1.1:
- Documentation update and fixes
Version 1.0:
- Inject resources into functional tests
- Ability to have partial mocks
Getting started
Include springtester
in your Play! application by adding the following in your dependencies.yml
:
- play -> springtester [version]
As springtester
depends on the existing Spring Play! module, you will have to include it as well:
- play -> spring [version]
Note: springtester
has currently only been tested with version 1.0.1
of the spring
module.
Then update your project’s dependencies to include springtester
and its dependencies:
play deps
Usage
Standard Mocks
The springtester
module assumes you are wiring your application using the spring
module. Here is an example of two classes being wired up using the spring
module. The first is a Person
class that injects an AgeCalculator
and calls the calculate(...)
method:
@Component
public class Person {
// Here we are injecting the AgeCalculator into the Person class
@Resource private AgeCalculator ageCalculator;
public boolean canVote() {
int age = calculator.calculate("1-January-1980");
return age >= 18;
}
}
Note that person itself is annotated with the Component
annotation. The AgeCalculator
will then take birth date of the Person
and calculate their age:
@Component
public class AgeCalculator {
public int calculate(String birthDate) {
// some code that figures out the age of the person
}
}
Now let’s say you want to write a test for Person
called PersonTest
and wish to replace the AgeCalculator
with a mock that returns the age of 20 instead of calculating the actual age of the Person
:
public final class PersonTest extends SpringMockitoUnitTestCase {
@Subject private Person subject;
@Mock(name = "ageCalculator") AgeCalculator mockAgeCalculator;
@Test
public void testCanVote() throws Exception {
Mockito.when(mockAgeCalculator.calculate("1-January-1980")).thenReturn(20);
boolean actual = subject.canVote();
assertEquals(true, actual);
}
}
How it works:
- The
PersonTest
extendsSpringMockitoUnitTestCase
. This superclass does all the magic to inject mocks into your objects.
- The annotation
Subject
will auto-magically inject the subject into the test. In the example above, aPerson
object will be injected into the test. Be sure that the subject is a Spring component by annotating it with @Component.
- The annotation
Mock
(provided byMockito
) is used to auto-magically create a mock of the given class. In the example above, anAgeCalculator
object will be mocked and set against the fieldmockAgeCalculator
. TheMock
annotation can take thename
property which specifies the name of the field the mock will replace in the subject. If the field name of the object to be mocked is the same as the field name in the subject, you may omit the name property like so:
// The name of the mock "ageCalculator" has the same name as the
// field "ageCalculator" found in the Person class and therefore
// does not require the "name" property.
@Mock AgeCalculator ageCalculator;
- The remainder of the example test above uses
Mockito
to mock the call to thecalculate
method and return 20 and then asserts that thesubject.canVote()
call will return true (since 20 is older than or equal to 18).
- The class
SpringMockitoUnitTestCase
extendsDatabaseUnitTestCase
which provides the helper methodcheckCount(Class<? extends Model> cls, long expected)
which can be used to check the row count of table.
- The
DatabaseUnitTestCase
will also delete the test in-memory database before each test method to ensure a clean run for each test method execution.
Partial Mocks
Similar to the annotation Mock
, you can also annotate using the annotation PartialMock
. This annotation exposes the partial mocking (i.e. spy) feature from Mockito. In the example below, we are stubbing a call to sport(). Any calls to number() will call the “real” code.
public final class PersonTest extends SpringMockitoUnitTestCase {
@Subject private Person subject;
@PartialMock(name = "favouriteThings") FavouriteThings partialMockFavouriteThings;
@Test
public void testSayFavouriteThings() throws Exception {
// Assuming sport() would normally return "football", the follow statement will ensure that "tennis" is returned instead.
// All other methods found on the partialMockFavouriteThings object will be called as normal (i.e. not stubbed out).
Mockito.when(partialMockFavouriteThings.sport()).thenReturn("tennis");
...
}
}
Note: Be sure to read the gotcha's on the official Mockito site when spying on real objects (partial mocks)
Functional Testing
- You can also inject mocks into Play’s FunctionalTesting framework by extending the class
SpringMockitoFunctionalTestCase
. In the case of functional tests, you do not need to specify a subject. In the example below, the test will make an HTTP request and all instances of AgeCalculator will be replaced with a mock:
public class FunctionalExampleTest extends SpringMockitoFunctionalTestCase {
@Mock(name = "ageCalculator") AgeCalculator mockAgeCalculator;
@Test
public void testSomething() {
// Do some mockito expectations
// This URL will hit a controller that will eventually inject the mocked AgeCalculator
Http.Response response = GET("/some/url");
// Verify response and expectations
}
}
- Sometimes you may want to inject mocks for certain test methods but not for other tests in the class. In that situation you can create your own mock (or any object you like) and inject it into Spring’s ApplicationContext like this:
@Test
public void testSomeTestMethod() {
// Create and register the mock object for just this test method
AgeCalculator mockAgeCalculator = Mockito.mock(AgeCalculator.class);
registerObject("ageCalculator", mockAgeCalculator);
// Do some mockito expectations
// Make your calls for testing
// Verify response and expectations
}
- You can inject resources into your tests. So if your test requires to use some component called
SomeObject
, then you can do this:
public class ExampleTest extends SpringMockitoUnitTestCase {
@Resource private SomeObject someObject;
@Test
public void testSomething() {
someObject.someMethod(...);
...
}
}
Source Code
If you wish to play with the source, please download the code here
To set up your project for IntelliJ just run these commands in the root springtester
directory:
- play deps --sync
- play idealize
- open the
springtester
project in IntelliJ - Make the
src
directory a source root