PowerMockito.whenNew
is a powerful function to stub a constructor. This article will demonstrate some scenario when we use whenNew and some gotchas I encountered along the way.
Let's say we have two classes, BookDao and BookRepository.
public enum BookDao {
INSTANCE;
public String getRecentBookTitle() {
BookRepository repository = new BookRepository();
return repository.queryBookTitle();
}
}
public class BookRepository {
public String queryBookTitle() {
return "xUnit Test Patterns";
}
}
The BookRepository is instantiated on getRecentBookTitle block. How do you test bookDao.getRecentBookTitle()?
@RunWith(PowerMockRunner.class)
@PrepareForTest({BookRepository.class})
public class BookDaoTest {
@Test
public void shouldReturnEffectiveJava() throws Exception {
String bookTitle = BookDao.INSTANCE.getRecentBookTitle();
// How to verify queryBookTitle?
// Mockito.verify(mock).queryBookTitle();
}
}
The ideal solution is to let BookDao to change its BookRepository class via constructor or setter injection. However, let's assume that we don't want to modify existing source code. Yet we still want to verify queryBookTitle is called.
This is where we can use PowerMockito whenNew. Using whenNew we can stub BookRepository creation with a mock.
BookRepository mock = Mockito.mock(BookRepository.class);
PowerMockito.whenNew(BookRepository.class).withAnyArguments().thenReturn(mock);
// Verify BookRepository is created
PowerMockito.verifyNew(BookRepository.class).withNoArguments();
You can even specify the constructor's arguments using withArguments
// Assume BookDao constructor have two arguments
PowerMockito.whenNew(BookRepository.class).withArguments(true, ArgumentMatchers.anyString()).thenReturn(mock);
Once we have BookRepositry mock, we can do many things including stubbing BookRepository queryBookTitle return value.
// Given
BookRepository mock = Mockito.mock(BookRepository.class);
Mockito.when(mock.queryBookTitle()).thenReturn("Effective Java");
PowerMockito.whenNew(BookRepository.class).withAnyArguments().thenReturn(mock);
// When
String bookTitle = BookDao.INSTANCE.getRecentBookTitle();
// Then
Assert.assertEquals("Effective Java", bookTitle);
Another use case is to capture BookRepository argument. In this sample, BookRepository has a new getBookByIsbn method that takes String as an argument.
// Given
String isbn = "978-0134685991";
ArgumentCaptor<String> isbnCaptor = ArgumentCaptor.forClass(String.class);
BookRepository mock = Mockito.mock(BookRepository.class);
PowerMockito.whenNew(BookRepository.class).withNoArguments().thenReturn(mock);
// When
String ignoredBookTitle = BookDao.INSTANCE.getBookByIsbn(isbn);
Mockito.verify(mock).queryBookTitle(isbnCaptor.capture());
// Then
Assert.assertEquals(isbn, isbnCaptor.getValue());
Now we can verify the isbn value in queryBookTitle is similar to what we have passed to BookDao. Pretty cool isn't?
Gotchas!
If you try to run the code above, you will likely to encounter these errors:
- Wanted but not invoked
- org.junit.ComparisonFailure
This is because we failed to stub BookRepository mock. But why? Didn't we already add BookRepository on @PrepareForTest? It's true, however PowerMockito requires us to specify the class that create BookRepository, which is BookDao. Not the BookRepository itself.
@RunWith(PowerMockRunner.class)
// You should also add BookDao on PrepareForTest! Even though you didn't mock or stub BookDao class.
@PrepareForTest({BookDao.class})
public class BookDaoTest {
@Test
public void shouldReturnEffectiveJava() throws Exception {
// ...
}
}
Now our test is all green.
Summary
Using PowerMockito.whenNew we can stub constructor with Mock object very easily. That being said, thinking twice about your current design and refactoring your source code is much better alternatives. Afterall, that's one of the reason why we do Unit Testing, to reduce code smell in our code. Here is the full source code for this article. I hope you find it useful.
BookDao.java
public enum BookDao {
INSTANCE;
public String getBookFromCache() {
BookRepository repository = new BookRepository(true, "http://example.com:4416");
return repository.queryBookTitle();
}
public String getRecentBookTitle() {
BookRepository repository = new BookRepository();
return repository.queryBookTitle();
}
public String getBookByIsbn(String isbn) {
BookRepository repository = new BookRepository();
return repository.queryBookTitle(isbn);
}
}
BookRepository.java
public class BookRepository {
// unused variables, for demonstration purpose only
private boolean useCache;
private String connectionUrl;
public BookRepository(boolean useCache, String connectionUrl) {
this.useCache = useCache;
this.connectionUrl = connectionUrl;
}
public BookRepository() {
this(false, "");
}
public String queryBookTitle(String isbn) {
return queryBookTitle();
}
public String queryBookTitle() {
return "xUnit Test Patterns";
}
}
BookDaoTest.java
import com.aldoapps.mybookshop.BookDao;
import com.aldoapps.mybookshop.BookRepository;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({BookDao.class})
public class BookDaoTest {
@Test
public void shouldReturnEffectiveJava1() throws Exception {
// Given
BookRepository mock = Mockito.mock(BookRepository.class);
Mockito.when(mock.queryBookTitle())
.thenReturn("Effective Java");
PowerMockito.whenNew(BookRepository.class)
.withAnyArguments()
.thenReturn(mock);
// When
String bookTitle = BookDao.INSTANCE.getRecentBookTitle();
// Then
PowerMockito.verifyNew(BookRepository.class)
.withNoArguments();
Assert.assertEquals("Effective Java", bookTitle);
Mockito.verify(mock)
.queryBookTitle();
}
@Test
public void shouldReturnEffectiveJava2() throws Exception {
// Given
BookRepository mock = Mockito.mock(BookRepository.class);
Mockito.when(mock.queryBookTitle())
.thenReturn("Effective Java");
PowerMockito.whenNew(BookRepository.class)
.withArguments(ArgumentMatchers.anyBoolean(),
ArgumentMatchers.anyString())
.thenReturn(mock);
// When
String bookTitle = BookDao.INSTANCE.getBookFromCache();
// Then
PowerMockito.verifyNew(BookRepository.class)
.withArguments(ArgumentMatchers.anyBoolean(),
ArgumentMatchers.anyString());
Assert.assertEquals("Effective Java", bookTitle);
Mockito.verify(mock)
.queryBookTitle();
}
@Test
public void shouldHaveSimilarIsbnValue() throws Exception {
// Given
String isbn = "978-0134685991";
ArgumentCaptor<String> isbnCaptor = ArgumentCaptor.forClass(String.class);
BookRepository mock = Mockito.mock(BookRepository.class);
PowerMockito.whenNew(BookRepository.class)
.withNoArguments()
.thenReturn(mock);
// When
String ignoredBookTitle = BookDao.INSTANCE.getBookByIsbn(isbn);
// Then
PowerMockito.verifyNew(BookRepository.class)
.withNoArguments();
Mockito.verify(mock)
.queryBookTitle(isbnCaptor.capture());
Assert.assertEquals(isbn, isbnCaptor.getValue());
}
}
Top comments (0)