DEV Community

Aldo Wachyudi
Aldo Wachyudi

Posted on

How to use PowerMockito whenNew

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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 {
       // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }

}
Enter fullscreen mode Exit fullscreen mode

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)