Wow its been a very busy few months here at Adaptavist so this is the first chance I've had in a while to blog, but I'd like to let you in on a little tip to ease a couple of common unit testing "problems" when developing JIRA plugins.
The Problems
These problems are not unique to JIRA but a consequence programming styles, hence you should be able to recognise and solve similar other problems whenever you come across them using the method I will describe. But first the problems.
The User Class
In JIRA the underlying OpenSymphony framework represents User entities with a final Class that can not be easily instantiated via constructor methods nor does it implement convenient interface which would allow us to mock or stub the class during testing. An example of where you might need to mock the User objects could be the following:bBased on the email address of the user who created an Issue send an email to all users in the priority-responders group.
One way of achieving this is to subclass AbstractIssueEventListener thus:
class PriorityIssueEventsListener extends AbstractIssueEventListener{ @Override public void issueCreated(IssueEvent event) { boolean isPriorityUser = isPriorityUserEmailAddress(event.getRemoteUser().getEmail()); if(isPriorityUser) { priorityAlertService.generateIssueCreatedEmails(event.getIssue()); } } private boolean isPriorityUserEmailAddress(String emailAddress) { ... } ... }
The ComponentManager
JIRA provides IoC and dependency injection and so generally should you need access to the ComponentManager singleton either setter or constructor injection can be used, however occasionally you will find yourself in a position where this either results in very ugly code or is just not possible. In these situations it is common to see something like
public void updateCustomField(Issue issue, MyCustomField field, Object newValue) {
try {
FieldLayoutManager fieldLayoutManager = ComponentManager.getInstance().getFieldLayoutManager();
FieldLayoutItem fieldLayoutItem = fieldLayoutManager.getFieldLayout(issue).getFieldLayoutItem(field);
ModifiedValue modifiedValue = new ModifiedValue(customField.getValue(issue), newValue);
IssueChangeHolder changeHolder = new DefaultIssueChangeHolder();
customField.updateValue(fieldLayoutItem, issue, modifiedValue, changeHolder);
}
catch(Exception e) { log.error("Error Updating Custom Field", e); }
}
Solution - Refactor For Test
Even from the early days of unit testing frameworks both these have been recognised difficulties and so, to my personal distaste, it is both possible and sometime common to see code being modified to make it testable often breaking design principals and practices. For example, in the first case, changing isPriorityUserEmailAddress to a protected method rather than private means that, provided your test class is in an identical package, you can omit testing issueCreated and rely testing isPriorityUserEmailAddress only. If the method is more complex (does more) than issueCreaated in our example a similar effect can be achieved by decomposing it in to several smaller protected functions by refactors. However, in general because a non-mockable object is being passed in as a method argument methods like issueCreated will not be directly testable using standard mocking techniques (proxying, stubbing, etc).
Why, although sometime the most sensible course of action, this "solution" is to my distaste is that a public method is a contribution to the API of the class whereas a protected method is not (it is more akin to private methods); when unit testing a class we should be seeking to test the behaviour, that is how the object behaves when other objects interact with it via the API. Therefore when we refactor for test we are no longer testing the behaviour of the the API method in question but instead the behaviour of the individual methods that compose it. Or in odd terms:
Given a function F() that can be decomposed to subfunctions f[i]() each with corresponding unit tests t[j]() where i = 0 .. x, j = 0 ... y, x <= y such that:
F() == f[0]() * ... *f[x]()
Then the unit test for F() is T(), and the approximation of this is defined by
T'() == t[0]() * ... * t[y]()
The Refactor for Test hypothesis is that T'() is a good enough approximation to T(), where "good enough" is satisfied if and only if for any two or more functions that interact (i.e one depends on the previous execution/output of the other) then a test exists for that composition. For example, suppose f[i+1]]() used the output of f[i]() then there exist tests t[i+1](), t[i](), t[i]() * t[i+1]().
Provided you have good testing of the composition of the combination of the protected methods generally there is adequate test coverage to ensure both correctness of code and behaviour.
Solution - PowerMock and Mockito
Mockito is arguably one of the best, if not the most popular JUnit extension (although it can be used in other places). It allows easy mocking of any non-final class or interface, which is particularly useful when unit tests involve "touching" third party APIs - like JIRA's **Manager classes. In the cases above Mockito alone can not solve the problems.
Enter PowerMock is an extension for Mockito that closes this gap.
PowerMock uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more.
PowerMock integrates tightly with Mockito and so there is little difference between creating a PowerMock or Mockito mock. For example, unit tests using PowerMock for our earlier problems might look like:
@RunWith(PowerMockRunner.class)
@PrepareForTest({User.class, ComponentManager.class})
public class PriorityIssueEventsListenerTest {
@Test
public void testIssueCreated() {
Issue issue ...
Map params ...
Long eventTypeId ...
User user = mock(User.class);
when(user.getName()).thenReturn("3Jane");
when(user.getEmail()).thenReturn("3Jane");
IssueEvent e = new IssueEvent(issue, params, user, eventTypeId);
listener.issueCreated(e);
...
}
@Test
public void testUpdateCustomField() {
ComponentManager mockComponentManager = mock(ComponentManager.class);
PowerMockito.mockStatic(ComponentManager.class);
when(ComponentManager.getInstance()).thenReturn(mockComponentManager);
...
}
}
To use PowerMock, make sure you have either followed the setup on the PowerMock site, or if you are using a manager like Nexus added the necessary mirroring repository; then simply add the following dependencies to your project POM:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock.modules</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock.api</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependencies>
<properties>
<powermock.version>1.3.6</powermock.version>
</properties>
It really is that simple.
Conclusion
Hopefully this gives you an extra tool in your testing tool box, but a word of warning: Prefer creating your own stub classes (either by implementing an interface or subclassing) over mocking using tools like Mockito and PowerMock. Over reliance on mocking frameworks tends towards tests that do not have reasonable or realistic expectations of the behaviour of the other classes the object under test interacts with. This can make the test less useful when performing refactorings or less resilient to changes in implementation (particular in 3rd party libraries). If a 3rd party API changes any stubs you have made for it will no longer compile, your mocks will still* compile (and in the worst case run!).
[* probably
]


Comments (3)
May 09, 2010
Edward Robertshaw says:
An alternative to using the mock framework is to extract the static method into ...An alternative to using the mock framework is to extract the static method into its own (none static) method.
Eg:
Then in the unit test override the method with one that returns a mock. To use M. Feathers term, creating a seam. So you have something like this when you need to new the object in the test suite:
Personally, I would love it if we could avoid mock frameworks entirely, for the reasons you detailed in your conclusions.
May 09, 2010
James McGivern says:
Absolutely, a great example of when refactoring for test is a good idea. However...Absolutely, a great example of when refactoring for test is a good idea. However the power of PowerMock extends beyond the case where you have control over the static call, for example
class MyClass { SomeLibraryClass obj public void myMethod() { obj.doThing("blah"); } } class SomeLibraryClass { public void doThing(String thing) { ComponentManager.getInstance().process(thing); } }Admittedly cases like these are in the minority. There's definitely a place for mocking frameworks, but used badly its like using TCDD to get rid of the weeds in your garden.
May 11, 2010
Shannon Krebs says:
Looks like some of the folks at Atlassian have also been investigating using Pow...Looks like some of the folks at Atlassian have also been investigating using PowerMock recently:
http://confluence.atlassian.com/display/DEV/FedEx+XIV+Delivery+Note+-+Static+Mocking