简体   繁体   中英

How can I unit test void functions?

class Elephant extends Animal {   
    public Elephant(String name) {
        super(name);
    }

    void makeNoise() {
        logger.info(" Elephant  make Sound");
    }

    void perform(String day) {
        if (day.equals("thursday") || day.equals("friday")) {
            makeNoise();
        }
    }
}

Now i want to test the perform method. How can I unit test this method using JUnit?

Solution with Mockito Spy

import org.junit.Test;

import static org.mockito.Mockito.*;

public class ElephantTest {

    @Test
    public void shouldMakeNoise() throws Exception {

        //given
        Elephant elephant = spy(new Elephant("foo"));

        //when
        elephant.perform("friday");

        //then
        verify(elephant).makeNoise();

    }
}

Negative tests:

@Test
public void elephantShouldDontMakeNoisesOnMonday() {

    //given
    Elephant elephant = spy(new Elephant("foo"));

    //when
    elephant.perform("monday");

    //then
    verify(elephant, never()).makeNoise();

}

or

@Test
public void shouldDoNotMakeNoisesOnMonday() {

    //given
    Elephant elephant = spy(new Elephant("foo"));

    //when
    elephant.perform("monday");

    then(elephant).should(never()).makeNoise();

}

Dependency

org.mockito:mockito-core:2.21.0

Read about

void() functions change the state of a program. This can be done by modifying a variable, a file, a database, etc.

In your case you're writing to a logger. If this results in writing " Elephant make Sound" to a file then you can read that file and see if the data in the file includes your noisy elephant.

If it however doesn't involve anything you can check (ie: it simply displays the output on the console) then you might want to look at some form of dependency injection (DI) where you can set the output to a file or something else you can easily read.

It should be noted that you can bypass DI by mocking the object and checking the appropriate methods are getting called.

To test any method, the responsibility to be tested must be visible from the out side of the method by changing state of any variable.

Typically it is done by returning value from the method. But without that, it can be done in many ways by modifying something from outside of the method scope, in case you have any "problem" to return something from the method!

In your case, you only log some message. And your code is not really testable in a sense that it does not do something that is directly related to changing the state of any variable (Because you change the state of other resource other than variable, that is not directly accessible by your code. You have to write some code to read the changes from that external resource, hence makes your testing code dependent to the reading also. If you have some problem with reading, your test case will not pass and that does not go with the spirit of the unit testing. The main idea is to reduce the dependency on external codes or libraries as much as possible). But your code can be testable by doing a slight refactoring / shifting responsiblity like below:

String makeNoise() {
    return "Elephant  make Sound";
}

String perform(String day) {
    if (day.equals("thursday") || day.equals("friday")) {
      return makeNoise();
    }
}

And then you shift the responsibility of logging the value returned from perform method to the one using it like below:

 logger.info(perform(day));

You have various options depending on the tools you are willing to use and the depth your tests should have.

Partial Mocking with plain Java

Create a class (MockElephant) that extends from elephant, overwrite makeNoise so it counts the number of invocations. Use that class in your test to check that makeNoise was called the correct number of times

Partial Mocking with a Framework You basically do the same as above but instead of manually coding the MockElephant you create it using some mocking framework. Makes the test much simpler, since you need less code. And it is easier to read. But if strange things happen it makes it harder to understand what is going on. In case of Mocking frameworks I think they are worth it.

The reasons why this is called Partial Mocking is that you mock only parts of a class (a single method in this case).

The alternative is to use Normal Mocks , which in your case seems feasible (but can become tough in legacy code).

Here you would inject the Logger as a dependency. For example you could create an additional constructor which allows you to provide the Logger. In your test you would then use a mocked Logger, which again counts it's invocations, probably along with the parameter it received and check that it has the expected values.

Again you can do that with a Mocking Framework or with plain old Java.

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