-1

How can one test a function that does not return a value, but uses given arguments to construct some values which are being sent to a sequential function?

For example

public void handleSomeEvent(String str1, String str2){
   MyObject obj1 = new MyObject();
   obj1.setParameterA(str1);
   obj2.setParameterB(str2);
   EventHandler.getInstance().notify(obj1)
}

In the above example i would like to verify EventHandler.notify was called with an object containing str1 as parameterA and str2 as parameter2. Can this be done using some common mocking frameowrk (i.e mockito)?

iddqd
  • 1,225
  • 2
  • 16
  • 34
  • Possible duplicate of [How to make mock to void methods with mockito](https://stackoverflow.com/questions/2276271/how-to-make-mock-to-void-methods-with-mockito) – Lino Jun 28 '18 at 12:43
  • 1
    https://stackoverflow.com/a/21116014/54734 – VoiceOfUnreason Jun 28 '18 at 12:44
  • What do you think you're actually testing when you write this? Basically all you're testing is that the setters work. In which case, `EventHandler` is redundant. You may as well just assert that `obj1.getParamA().equals(str1)` etc – Michael Jun 28 '18 at 12:49
  • @Lino - Please read both threads again, it is not the same questiom. – iddqd Jun 28 '18 at 12:50
  • @VoiceOfUnreason Also, Not the same question – iddqd Jun 28 '18 at 12:50
  • @Michael Well, obviously this is a simplified example. But for the sake of the argument - YES, i want to verify the setters were called correctly and not for example setParamererA(str2) instead of setParameterA(str1). This is what unit testing is for. – iddqd Jun 28 '18 at 12:57
  • 1
    @ABR My point is that this is not a unit test. You are testing `MyObject` and `EventHandler` together when both can (and should) be tested independently. Test `EventHandler` with a mock `MyObject`. Test `MyObject` (if you really think there's value in testing setters - *and I don't*) by using the getters. – Michael Jun 28 '18 at 12:59
  • @Michael I am not testing EventHandler, i do not care about what happens once it is called, only that it is called with the right argument - which is under handleSomeEvent responsibilities. – iddqd Jun 28 '18 at 13:02
  • If you're not testing `EventHandler`, you shouldn't be calling any methods of `EventHandler`. It's as simple as that. If I change `EventHandler.notify` to always throw a RuntimeException, I've broken your test even though you are "not testing EventHandler". – Michael Jun 28 '18 at 13:03
  • @Michael Take a look at the latest answer, it addresses this issue as well. – iddqd Jun 28 '18 at 13:19
  • @Michael I think you need to read more on unit tests my friend, then read this post again – iddqd Jun 28 '18 at 13:28
  • @ABR And you too – Michael Jun 28 '18 at 13:32
  • @Michael On a side note, i might have been more inclined to agree with you if you did anything else than say what you think i am doing wrong and actually suggest an answer, or explaining what a solution that you think is correct would look like. Am i supposed to take you seriously for simply heckling me? – iddqd Jun 28 '18 at 13:35
  • I am not heckling you and I already suggested [what you should do](https://stackoverflow.com/questions/51083214/how-to-test-a-void-function-with-sequential-calls?noredirect=1#comment89155407_51083214). Someone upvoted it so it must make some kind of sense. – Michael Jun 28 '18 at 13:38
  • @Michael And i really don't think you understand my question, so i will phrase it one more time - This function is responsible for one thing, and that is for building an MyObject instance, then forwarding it to EventHandler (two things actually). in my opinion - that is what the unit test should verify: That the object was constructed, and forwarded. Nothing more, nothing less. – iddqd Jun 28 '18 at 13:39
  • @Michael No, you told me i was wrong and thati sould test MyObject and EventHandler, which is obviously not what this thread is about. – iddqd Jun 28 '18 at 13:40
  • "That the object was constructed, and forwarded" There you go. The crux of the issue in 7 words. It's not a unit test. A unit test would be 1) test that the object is constructed (use getters - pointless IMO but fine) or 2) test that `notify` does what it's supposed to. First issue: you're testing 2 things (note the word 'and'). Second issue: "test that X is forwarded" is not a valid unit test - what do you think you'd be testing, that Java's method invocations work? – Michael Jun 28 '18 at 13:46
  • @Michael I am testing the function, i have no idea why you think that testing two things is an issue, or why you think the word "and" is taboo.Anyway, I can just rephrase it as "I am testing that the proper value was forwarded".Exactly the same thing would be tested for a non void function except with the word "returned" instead of "forwarded". the issue in hand is that there is no return value, so there is no way for me to do MyObject a= handleSomeEvent("a","b"). MyObject is the defacto return value of the function - and that is the ONLY thing that is tested in the end. – iddqd Jun 28 '18 at 13:58

3 Answers3

3

You can do following

@RunWith(PowerMockRunner.class)
@PrepareForTest({ EventHandler.class })
public class UnderTestTest {

@Test
public void testLoadUniqueOrNull() throws NKFException {
    PowerMockito.mockStatic(EventHandler.class);

    EventHandler handler = PowerMockito.mock(EventHandler.class);
    PowerMockito.when(EventHandler.getInstance())
            .thenReturn(handler);

    ArgumentCaptor<MyObject> handlerArg = 
    ArgumentCaptor.forClass(MyObject.class);
    PowerMockito.doNothing()
            .when(handler)
            .notify(handlerArg.capture());

    new UnderTest().handleSomeEvent("test");
    Assert.assertEquals(new MyObject("test"), handlerArg.getAllValues()
            .get(0));
}

}


public class UnderTest {
    public void handleSomeEvent(String str1) {
        MyObject obj1 = new MyObject(str1);

        EventHandler.getInstance()
                .notify(obj1);
    }
}


public class MyObject {

    private final String x;

    public MyObject(String x) {
        this.x = x;
    }

    @Override
    public boolean equals(Object obj) {
        return ((MyObject) obj).x == x;
    }
}


public class EventHandler {

    private final static EventHandler e = new EventHandler();

    public static EventHandler getInstance() {
        return e;
    }

    public void notify(MyObject obj) {
        // whatever
    }
}

(side note this quick code is to demonstrate the functionality not best practises in coding)

Michal
  • 970
  • 7
  • 11
  • Worked like a charm! No need to override equals though, handlerArg.getAllValues().get(0) retireved MyObject that EventHandler received and then its attributes (i.e str1 and str2) can be asserted separately. Thanks! – iddqd Jun 28 '18 at 13:16
  • While your answer is technically correct it directs the OP to write [STUPID code](https://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/). We should better point her to write SOLID code... – Timothy Truckle Jul 03 '18 at 11:18
2

In cases when need to verify called parrams use ArgumentCaptor<T> by Mockito

See the reference: https://static.javadoc.io/org.mockito/mockito-core/2.6.9/org/mockito/ArgumentCaptor.html

0

Unittest verify public observable behavior where "public" means through the units API and "behavior" meand return values and/or communication with dependencies.

Your code has a hidden dependecy intoduced by the static access to the EventHandler object. I assume that your EventHandler class incorporates the Java singelton pattern. This is STUPID code most of us have been started with.

You should not surrender to this bad design by using PowerMock.

The better way would be to pass an EventHandler object as constructor parameter to your code under test like this:

    class MyTestedClass{
       private final EventHandler eventHandler;
       class MyTestedClass(EventHandler eventHandler){
          this.eventHandler=eventHandler;
       }

       public void handleSomeEvent(String str1, String str2){
          MyObject obj1 = new MyObject();
           obj1.setParameterA(str1);
           obj2.setParameterB(str2);
           eventHandler.notify(obj1)
        }
    }

And then make your EventHandler a normal class that you can inherit from. (at least remofe the final key word from the class declaration).

Then you could use plain Mockito to replace the EventHandler object with a test double and verify your codes comunication with that test double:

    class MyTestedClassTest{
       @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 
       @Mock private EventHandler eventHandler; // ignores the private constructor
       class MyTestedClass(EventHandler eventHandler){
          this.eventHandler=eventHandler;
       }

       @Test
       public void notifiesEventHandlerWithDTO(String str1, String str2){

           new UnderTest(eventHandler).handleSomeEvent("test1","test2");

           ArgumentCaptor<MyObject> handlerArg = ArgumentCaptor.forClass(MyObject.class);
           verify(eventHandler).notify(handlerArg.capture());
           assertThat("parameter A passed to object",
                       handlerArg.getValue().getParameterA(),
                       equalTo("test1"));
           assertThat("parameter B passed to object",
                       handlerArg.getValue().getParameterB(),
                       equalTo("test2"));
        }
    }
Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51