Home > database >  PowerMockito returns null when mocking private method for testing public method
PowerMockito returns null when mocking private method for testing public method

Time:09-29

I'm trying to write a Test that will compare strings' equality.

Here is a code snippet of the class that should be tested

package ge.jibo.util;

public class TextManager {

  private String concatenateTwoString(String t1, String t2) {
    return t1   t2;
  }

  public String concatenate(String t1, String t2) {
    return concatenateTwoString(t1, t2);
  }

}

and here is a test class

package ge.jibo.util;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;

import static org.assertj.core.api.Assertions.assertThat;

public class TextManagerTest {

  @Test
  @PrepareForTest(TextManager.class)
  void checkIfConcatenationWorks() throws Exception {
    TextManager textmanager = PowerMockito.spy(new TextManager());
    PowerMockito.doReturn("someText").when(textmanager,"concatenateTwoString", Mockito.anyString(), Mockito.anyString());
    String text = textmanager.concatenate("ji","bo");
    assertThat(text).isEqualTo("jibo");
  }
}

as you see, I want to test the public method concatenate, which calls the private method concatenateTwoString in the same class. The idea is that I want to make a mock object for the private method, and whenever it will be called from the public method it should return the constant value "someText"

but it returns the null instead of "someText" from the private method concatenateTwoString.

org.opentest4j.AssertionFailedError: 
Expecting:
 <null>
to be equal to:
 <"jibo">
but was not.
Expected :jibo
Actual   :null

Does anyone know how to fix it?

Dependency Versions:

  1. junit-jupiter - 5.7.2
  2. junit-platform-launcher - 1.8.1
  3. mockito-core - 3.12.4
  4. mockito-junit-jupiter - 3.12.4
  5. powermock-core - 2.0.9
  6. powermock-api-mockito2 - 2.0.9

CodePudding user response:

As @heaprc mentioned in his post, the "Test" method can be executed correctly, in the case when we use JUnit4 instead of JUnit5 and by adding JUnit4 annotation for powerMock @RunWith(PowerMockRunner.class).

Everything is correct, there is no direct solution to mock the private method under the public one in the case of JUnit5.

So what options do we have?

In m.o there is three option in this case:

  1. leave JUnit5 for the "Test framework" and do not try to make a mock of the private method. At first, think about if you really want to test that private method and if it's necessary to test make it package-private (by changing the method to package-private you are breaking the concept of encapsulation, but if you relly need to test it, then I think it's not a big problem)
  2. downgrade your "Test framework" from JUnit5 to JUnit4. but in this case, if you already wrote 100 tests in JUnit5 you will have to change all of them to work on JUnit4. The fact that you have to downgrade your framework is really annoying, and the fact that you have to rewrite 100 tests for JUnit4 is very irritant.
  3. make JUnit4 and JUnit5 work together. I think this is the better solution and here is the example:

you have to have the following dependencies in your pom.xml file.

pom.xml

 <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>5.8.1</version>
    </dependency>


    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.9</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.9</version>
        <scope>test</scope>
    </dependency>
  • junit-vintage-engine allows you to run JUnit4 test.
  • junit-jupiter-engine allows you to run JUnit5 test.

Code snippet

    import org.junit.Assert;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mockito;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    
    @PrepareForTest(TextManager.class)
    @RunWith(PowerMockRunner.class)
    public class TextManagerTest {
    
        @org.junit.Test  //Test using Junit4
        public void checkIfConcatenationWorksWithJunit4() throws Exception {
            TextManager textmanager = PowerMockito.spy(new TextManager());
            PowerMockito.doReturn("jibo").when(textmanager, "concatenateTwoString", Mockito.anyString(), Mockito.anyString());
            String text = textmanager.concatenate("ji", "bo");
            Assert.assertEquals(text,"jibo");
        }
    
        @Test  //Test using Junit5
        public void checkIfConcatenationWorksWithJunit5() {
            Assertions.assertEquals("text","jibo");
        }
    }

in this case, you will run the checkIfConcatenationWorksWithJunit4 test method with the Junit4 platform, and @RunWith(PowerMockRunner.class) will work for this execution. in the case of checkIfConcatenationWorksWithJunit5 method, it will run with the Junit5(Jupiter) platform.

Finally

you have to add maven-surefire-plugin in the pom.xml file to make both JUnit4 and JUnit5 tests to be tested while building it or testing using maven commands.

   <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

without adding maven-surefire-plugin the only one platform (JUnit4) test will be executed at runtime.

CodePudding user response:

You have forgotten to add @RunWith and specify the PowerMockRunner

If you are able to use Junit-4 this is the solution.

@RunWith(PowerMockRunner.class)
@PrepareForTest(TextManager.class)
public class TextManagerTest {

  @Test
  public void checkIfConcatenationWorks() throws Exception {
    // Arrange
    TextManager textmanager = PowerMockito.spy(new TextManager());
    PowerMockito.doReturn("someText").when(textmanager,"concatenateTwoString"
    , Mockito.anyString()
    , Mockito.anyString());

    // Act
    String text = textmanager.concatenate("ji","bo");

    // Assert
    assertThat(text).isEqualTo("jibo");
  }
}
  • Related