Mock construction of new objects

来源:互联网 发布:淘宝营养素 编辑:程序博客网 时间:2024/06/06 15:45

Quick summary

  1. Use the @RunWith(PowerMockRunner.class) annotation at the class-level of the test case.
  2. Use the @PrepareForTest(ClassThatCreatesTheNewInstance.class) annotation at the class-level of the test case.
  3. Use PowerMock.createMock(NewInstanceClass.class) to create a mock object of the class that should be constructed (let's call itmockObject).
  4. Use PowerMock.expectNew(NewInstanceClass.class).andReturn(mockObject) to expect a new construction of an object of typeNewInstanceClass.class but instead return the mock object.
  5. Use PowerMock.replay(mockObject, NewInstanceClass.class) to change the mock object and class to replay mode, alternatively use the PowerMock.replayAll() method.
  6. Use PowerMock.verify(mockObject, NewInstanceClass.class) to change the mock object and class to verify mode, alternatively use the PowerMock.verifyAll() method.

Example

Let's say you have a class like this:

public class PersistenceManager {        public boolean createDirectoryStructure(String directoryPath) {                File directory = new File(directoryPath);                if (directory.exists()) {                        throw new IllegalArgumentException("\"" + directoryPath + "\" already exists.");                }                return directory.mkdirs();        }}

What's interesting here is the creation of a new File object. This is interesting because in order to get 100% code coverage of this method we need to be able test the method both when the directory.exists() query return true and false. But in order to unit test this method one needs to substitute the File object created in this method for a mock implementation. One way to do this is to extract the call to new File(..)to a package private method which is overridden in the test to return a File mock. But with PowerMock you don't have to change the code, instead you can instruct PowerMock to intercept the call to new File(..) and return a mock object instead. To do this we start by creating a mock of type File.class as usual:

File fileMock = createMock(File.class);

To expect the call to new File we simply do:

expectNew(File.class, "directoryPath").andReturn(fileMock);

The "directoryPath" is the constructor parameter passed to the File constructor. That's it when it comes to the expectation behavior! There are however some things that must be explained further. First of all you need to tell PowerMock which classes to prepare for test and in this case this means the class that does the actual new call, in this case it's the PersistenceManager class that does new File(..). So in order to provide PowerMock with this information we add @PrepareForTest(PersistenceManager.class) at the class-level of test case. The reason why we're not preparing File.class is that a new instance of the File is never created when doing expectNew, PowerMock just intercepts the call to new from the class that does new and thus you need to prepare the PersistenceManager class in this example. As usually you also need to tell JUnit to execute the test using PowerMock which is done by using the @RunWith(PowerMockRunner.class) annotation. Without these two annotations the test will fail!

The last aspect is the replay and verify behavior. In order to correctly replay the expected behavior we need to replay both the fileMock object and the File class. I.e.

replay(fileMock, File.class);

The verification procedure follows the same rules:

verify(fileMock, File.class);

That's it!

If you're using PowerMock >= 0.9 you could alternatively use the replayAll and verifyAll methods instead. This will automatically replay and verify all class- and object mocks created by PowerMock.

The full test simulating when a directory previously didn't exist looks like this:

@RunWith(PowerMockRunner.class)@PrepareForTest( PersistenceManager.class )public class PersistenceManagerTest {        @Test        public void testCreateDirectoryStructure_ok() throws Exception {                final String path = "directoryPath";                File fileMock = createMock(File.class);                PersistenceManager tested = new PersistenceManager();                expectNew(File.class, path).andReturn(fileMock);                expect(fileMock.exists()).andReturn(false);                expect(fileMock.mkdirs()).andReturn(true);                replay(fileMock, File.class);                assertTrue(tested.createDirectoryStructure(path));                verify(fileMock, File.class);        }}

There's also a slightly shorter way to write the test. In simple situations like the example above you can create the mock object of the File class and expect the call to new File at the same time using the createMockAndExpectNew method in PowerMock. The test could thus be rewritten as:

@RunWith(PowerMockRunner.class)@PrepareForTest( PersistenceManager.class)public class PersistenceManagerTest {        @Test        public void testCreateDirectoryStructure_usingCreateMockAndExpectNew() throws Exception {                final String path = "directoryPath";                File fileMock = createMockAndExpectNew(File.class, path);                PersistenceManager tested = new PersistenceManager();                expect(fileMock.exists()).andReturn(false);                expect(fileMock.mkdirs()).andReturn(true);                replay(fileMock, File.class);                assertTrue(tested.createDirectoryStructure(path));                verify(fileMock, File.class);        }}

More features

expectNew works similar to the normal expect method in EasyMock. This means that you can setup expectations to be invoked a certain number of times or throw an exception upon instantiation etc. For example let's say that you for some reason would like to throw an IllegalStateException by the time new File(..) is invoked. You can do this by:

expectNew(File.class, "path").andThrow(new IllegalStateException("testing!");

As another example imagine that a method creates two new File objects and you like to return the same mock instance for both. You could do this by:

expectNew(File.class, "path").andReturn(fileMock).times(2);