sam
sam

Reputation: 13

Java Unit Test: Test from public method or package private method

I have a class Price with a public method calculate() which calls another private method calculateByA().

public class Price {
    public SomeObject calculate() {
        if(someCondition) {
            calculateByA()
            // ....
        } else {
            //Calculate something else
        }
    }

    private int calculateByA() {
        //calculation logic
    }
}

I want to make a unit test for testing the logic of calculationByA().

I have thought of:

  1. Test by calling public method calculate() and set someCondition to true.
  2. Change calculateByA() to package-private and call it in the test directly.

However, I am not sure which approach is better.

Upvotes: 0

Views: 2724

Answers (3)

Tiemo Vorschütz
Tiemo Vorschütz

Reputation: 188

PowerMock lets you test your private method directly:

Price.java:

import java.util.concurrent.ThreadLocalRandom;

public class Price {

  private final boolean someCondition;


  public Price(final boolean someCondition) {

    this.someCondition = someCondition;
  }


  public int calculate() {
    if (someCondition) {
      return calculateByA();
    } else {
      // Calculate something else
      return 0;
    }
  }


  private int calculateByA() {
    final int randomNum = ThreadLocalRandom.current().nextInt(0, 10 + 1);
    return 4711 * randomNum;
  }
}

PriceTest.java:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Price.class)
public class PriceTest {

  /*
   * Refelection test for a single private method
   */
  @Test
  public void calculateByA() throws Exception {

    final Price instance = new Price(true);
    final int result = Whitebox.invokeMethod(instance, "calculateByA");
    System.out.println(result);
  }


  /*
   * Test public method calculate which invokes private method calculateByA
   */
  @Test
  public void testCalculate() throws Exception {

    final Price instance = new Price(true);
    System.out.println(instance.calculate());
  }
}

Dependencies next to JUnit4:

</properties>
     <powermock.version>2.0.2</powermock.version>
</properties>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.28.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-core</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>

Upvotes: -1

James Wilson
James Wilson

Reputation: 1636

There are three options to your question. Ultimately it is up to you which is best for your situation. However, here are some thoughts on how you can choose between them:

  1. Test the method via the public interface

In your case as you mention you can call calculate with the correct configuration to execute calculateByA. This I believe is the best approach. The code has been structured to mean that the calculate is the public interface by which calculateByA is accessed. This means that your test will alert people to issues when this behaviour changes. Which in turn means the user of the class will experience a change in behaviour.

  1. Change the definition of to protected

As you mention, this will then allow you to call calculateByA from other classes in the same package including unit test classes. This is certainly an approach that will allow for easy and independent unit testing of calculateByA. However, I don't like this, simply because there is a reason you only want access via the calculate function and by changing the access it is tempting for other classes in the same package to call calculateByA directly.

  1. Use reflection to invoke private method directly

There are a number of technologies that allow you to invoke a private method directly in tests. I won't repeat the excellent answer from by @Tiemo Vorschütz which goes into the details of how to do this. However, this would be my last resort. Simply tests with reflection are more brittle and increase the cost of maintenance of the test suite.

Upvotes: 0

Christian Sauer
Christian Sauer

Reputation: 10889

I strongly dislike the usage of powermock et al. The problem is not that they let you bypass the visibilitysystem (which other language not bother to have at all, see python for example), but that you try to test an implementation detail.

Write your test for visible methods and observe side effects of their private methods. In your example, e.g. test that in someCondition, your results match the expectations in that case. If you have problems because there are a lot of private methods, consider using a package and split your code up - it's probably too large anyway. Also use DI luke!

Upvotes: 2

Related Questions