简体   繁体   中英

Unit Test public method that calls private method and other objects in Java

I am new to Unit Testing and recently tried my hands on JUnit test and Mockito.

I am trying to unit test a method that calls multiple private methods and also creates private objects of other classes.

How can I unit test the method.

For Example if I have the following code:

class ClassToTest {
    private OuterClass outerClass;
    public Boolean function() {
        outerClass = new OuterClass(20);
        return innerFunction(outerClass.getValue());
    }

    private Boolean innerFunction(int val) {
        if (val % 2 == 0)
            return true;
        return false;
    }
}

I'm confused how would I test the public function.

It doesn't matter how a method is implemented; that method should have a contract it obeys and that is what you are verifying with your test. In this example, function() should return true if the outerClass 's value is even. One way to accomplish this would be to inject (pass into the ClassToTest constructor) the instance of outerClass so that you can control the value when testing:

@Test
public void trueWhenEven() {
    var outer = new OuterClass(2);
    var ctt = new ClassToTest(outer);
    assertTrue(ctt.function());
}

Sometimes the contract is just that a method invokes methods on some other objects; in these kinds of cases, you can use Mockito or similar library to verify the interactions.

You are starting unit-testing with directly going at some non-trivial questions.

Question 1: How to handle implementation details / private functions? Answer: Unit-testing is about finding the bugs in your code, and that is one primary goal of unit-testing (and most other kinds of testing). Another primary goal is to prevent the introduction of bugs by acting as regression tests when the software is changed. Bugs are in the implementation - different implementations come with different bugs. Therefore, be sure to test the implementation details. One important tool to support here is coverage analysis, which shows you which parts of the implementation's code have been reached by your tests.

You may even test aspects beyond the contract of the function: a) Negative tests are tests that intentionally check behaviour for invalid / unspecified inputs, and are important to make a system secure. Because, even when provided with invalid input, the system should not allow to be hacked because of, for example, reading or writing out-of-bounds memory. This, however, does probably not apply for your example, because your method most likely is specified to implements a 'total function' rather than a 'partial function'. b) Tests of implementation details (if accessible) can even be performed beyond what is needed by the current implementation. This can be done to prevent bugs in upcoming changes to a component, like, extensions of the API.

There are, however, also secondary goals of unit-testing. One of them is to avoid that your tests break unnecessarily when implementation details change. One approach to also reach the secondary goal is, to test the implementation details via the public API. This way certain kinds of re-design of the implementation details will not break your tests: Renaming, splitting or merging of private functions would not affect the tests. Switching to a different algorithm, however, likely will require you to re-think your tests: Tests for an iterative / recursive implementation of the fibonacci function will look different than for an implementation using the closed-form-expression from Moivre/Binet, or for a lookup-table implementation.

For your example this means, you should try to test the functionality of your private function via the public API.

Question 2: How to deal with dependencies to other parts of the software? Unit-testing focuses on finding the bugs in small, isolated code pieces. When these code pieces have dependencies to other code parts, this can negatively influence your ability to unit-test them properly. But whether this is really the case depends on the actual dependency. For example, if your code uses the Math.sin() function, this is also a dependency to a different code part, but such a dependency does typically not harm your ability to properly test the code.

Dependencies to other components bother you in the following cases: The use of the other components makes it difficult to stimulate all interesting scenarios in your code under test. Or, the use of the other component leads to non-deterministic behaviour (time, randomness, ...). Or, the use of the other components causes unacceptably long build or execution times. Or, the other component is buggy or not even available yet.

If all of these criteria are not met (as it is normally the case with the Math.sin() function), you can typically just live with the other components being part of your tests. You should, however, keep in mind that in your unit-tests you still focus on the bugs in your code and do not start to write tests that actually test the other components: Keep in mind that the other components have tests of their own.

In your example you have chosen Outerclass to have some apparently trivial functionality. In this case you could live with Outerclass just remaining part of your tests. However, it is only an example from your side - The real other class may in fact be disturbing according to the above criteria. If that is the case, then you would somehow have to manage that dependency, which all requires in some way to come to a design that is testing-friendly.

There is a whole family of approaches here, so you better search the web for "design for testability" and "inversion of control". And, you also should try to learn about what distinguishes unit-testing and integration testing: This will help you to avoid trying to apply unit-testing on code parts that should rather be tested with integration testing.

Generally with Mockito this would require the use of Dependency Injection and then you would inject a mock of OuterClass for the test.

If you'd really like to test this without a Spring type framework being added I can think of 3 options:

1) Make this an Integration Test and test the real instances of everything

2) Alter your code so that OuterClass is created via a passed in Object from a setter or a constructor and then pass in a mock for your test

3) Change private OuterClass outerClass; to protected OuterClass outerClass; and make sure your test package structure is the same as your actual code package structure and then you can do outerClass = Mockito.mock(OuterClass); in your test set up.

I was able to test my public method which in turns calls a private method. I used ReflectiontestUtils of springframework. Find the example below.

@Service
public class ClassTobeTested{
    
   @Autowired
   private SomeService someService;

   @override
   public String methodToTest(String arg){
      // some task
      someLogic(arg);
      //some task
      return "Success";
   } 

   private Boolean someLogic(String arg){
      return someService.performLogic(arg);
   }

}

Testing

import org.springframework.test.util.ReflectionTestUtils;

public Testing{

   @InjectMocks
   ClassTobeTested classTobeTested;
   @Mock
   SomeService someService;

   @Test
   public testmethod{
      when(ReflectiontestUtils.invokeMethod(classTobeTested, "someLogic", "abc")).thenReturn(true);
      String str = classTobeTested.methodToTest("abc");
      assertEquals(str,"Success");
   }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM