Introduction
This article is not another diatribe to tell you the importance of unit testing. I think we can all agree that it is important. This is about solving an issue that comes up frequently in unit testing. How do I test static methods, and how do you test private methods.Until PowerMock, most developers were told you can't really test a static method per se. I will show how to do it below.
A common mechanism for testing private methods is to change them to protected. This is not a good solution for a number of reasons. So how do we test them? Again, PowerMock has come to the rescue.
I want solutions. I use Arquillian, but I am not such a zealot that I will chant the mantra that mocking is evil and should not be done. It is a means to an end. That being said, we use Mockito which in my professional experience is the best current mocking framework available. It is easy to setup and use.
However, Mockito has its limitations. Two are mentioned above. The fine developers of PowerMock have come up with an additional framework to use in conjunction with Mockito to get around the limitations.
Solution
The Apache Maven sample code for the project was developed using NetBeans and can be found on Bitbucket here: unit-testing-mockito-powermockito.Since I am a Apache Maven user, I simply add the relevant frameworks to my
pom.xml
as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | < dependencies > < dependency > < groupid >junit</ groupId > < artifactid >junit</ artifactId > < version >4.11</ version > < scope >test</ scope > < type >jar</ type > </ dependency > < dependency > < groupid >org.mockito</ groupId > < artifactid >mockito-core</ artifactId > < version >1.9.5</ version > < scope >test</ scope > </ dependency > < dependency > < groupid >org.powermock</ groupId > < artifactid >powermock-module-junit4</ artifactId > < version >1.5.1</ version > < scope >test</ scope > </ dependency > < dependency > < groupid >org.powermock</ groupId > < artifactid >powermock-api-mockito</ artifactId > < version >1.5.1</ version > < scope >test</ scope > </ dependency > </ dependencies > |
PowerMockRunner
to help me with my testing. The following are some simple classes to demonstrate how to test. The first is a class to generate UUID values for IDs.
IdentityUtilities.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.bluelotussoftware.example; import java.util.UUID; /** * * @author John Yeary */ public class IdentityUtilities { /** * Generates a {@link UUID} * * @return a {@code String} representation of a UUID. */ public static String getUUID() { return UUID.randomUUID().toString(); } } |
generateId
.
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | package com.bluelotussoftware.example; import java.text.MessageFormat; import java.util.Objects; /** * * @author John Yeary */ public class Person { private String id; private String firstName; private String lastName; public Person() { } public void initialize() { if ( null == id) { id = generateId(); } } private String generateId() { return IdentityUtilities.getUUID(); } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this .lastName = lastName; } public String getId() { return id; } public String getFullName() { return MessageFormat.format( "{0} {1}" , firstName, lastName); } @Override public int hashCode() { int hash = 7 ; hash = 97 * hash + Objects.hashCode( this .id); hash = 97 * hash + Objects.hashCode( this .firstName); hash = 97 * hash + Objects.hashCode( this .lastName); return hash; } @Override public boolean equals(Object obj) { if (obj == null ) { return false ; } if (getClass() != obj.getClass()) { return false ; } final Person other = (Person) obj; if (!Objects.equals( this .id, other.id)) { return false ; } if (!Objects.equals( this .firstName, other.firstName)) { return false ; } if (!Objects.equals( this .lastName, other.lastName)) { return false ; } return true ; } @Override public String toString() { return "Person{" + "id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + '}' ; } } |
PersonTest
class to test our Person
object. The explanation follows the code.
PersonTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | package com.bluelotussoftware.example; import org.junit.Test; import static org.junit.Assert.*; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.mockito.Mockito.*; import org.powermock.api.mockito.PowerMockito; import static org.powermock.api.mockito.PowerMockito.verifyPrivate; /** * * @author John Yeary */ @RunWith (PowerMockRunner. class ) @PrepareForTest ({IdentityUtilities. class , Person. class }) public class PersonTest { public PersonTest() { } /** * Test of initialize method, of class Person. */ @Test public void testInitialize() { System.out.println( "initialize" ); PowerMockito.mockStatic(IdentityUtilities. class ); when(IdentityUtilities.getUUID()).thenReturn( "ABC-123" ); Person instance = new Person(); instance.initialize(); String result = instance.getId(); assertEquals(result, "ABC-123" ); } @Test public void testGenerateId() throws Exception { System.out.println( "generateId()" ); Person instance = PowerMockito.spy( new Person()); /* * Setup the expectation to the private method using the method name */ PowerMockito.when(instance, "generateId" ).thenReturn( "UNIT-1A" ); instance.initialize(); assertEquals( "UNIT-1A" , instance.getId()); // Optionally verify that the private method was actually called verifyPrivate(instance).invoke( "generateId" ); } @Test public void ObjectsNotEqual() { System.out.println( "Objects not Equal" ); Person p = new Person(); Object o = new Object(); assertFalse(p.equals(o)); } } |
@RunWith(PowerMockRunner.class)
annotation tells jUnit that we are using an extension to handle testing of this class. This tells jUnit to use this test runner instead of its own test runner.
The
@PrepareForTest({IdentityUtilities.class, Person.class})
serves multiple purposes here. The first is to tell PowerMock that we need to do some preparation of the classes to instrument them for our testing. Classes defined using this annotation are typically those that needs to be byte-code manipulated. This includes final classes, classes with final, private, static or native methods that should be mocked and also classes that should be return a mock object upon instantiation. IdentityUtilities.class
is our class with a static method, and Person.class
contains our private method.
The first test
testInitialize()
behaves like any other Mockito test with the exception that we mock it using PowerMockito.mockStatic(IdentityUtilities.class)
to initialize it. This makes testing static methods as easy as any other Mockito test.
The second test requires a little more explanation. In this case, we spy on the object we are creating. This allows us to instrument it. Then using the "spied" instance we use
PowerMockito.when(instance, "generateId").thenReturn("UNIT-1A")
to tell the framework that when the private method "generateId" is called, we want to return a specific value. Then we initialize the instance, and check the value is what we expected. Finally, we can optionally check to make sure that the method was actually called by using verifyPrivate(instance).invoke("generateId")
.
0 comments :
Post a Comment