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
PersonTestextendsSpringMockitoUnitTestCase. This superclass does all the magic to inject mocks into your objects.
- The annotation
Subjectwill auto-magically inject the subject into the test. In the example above, aPersonobject 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, anAgeCalculatorobject will be mocked and set against the fieldmockAgeCalculator. TheMockannotation can take thenameproperty 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
Mockitoto mock the call to thecalculatemethod and return 20 and then asserts that thesubject.canVote()call will return true (since 20 is older than or equal to 18).
- The class
SpringMockitoUnitTestCaseextendsDatabaseUnitTestCasewhich provides the helper methodcheckCount(Class<? extends Model> cls, long expected)which can be used to check the row count of table.
- The
DatabaseUnitTestCasewill 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
springtesterproject in IntelliJ - Make the
srcdirectory a source root