5

Hi i have a class like this

class MyClass {

  private ExternalClass1 ex1;
  private ExternalClass2 ex2;
  private ExternalClass3 ex3

public String doSomething(String arg1){ 
 val1=ex1.invoke(arg1); 
 val2=ex2.call(val1); 
 result=ex3.doit(val2);
 return result;
}    

}

unit test of this method

@Test
void doSometing(){
   ExternalClass1 ex1=mock(ExternalClass1);
   ExternalClass2 ex2=mock(ExternalClass2);
   ExternalClass3 ex3=mock(ExternalClass2);

when(ex1.invoke(arg1)).thenReturn(val1);
when(ex2.call(val1)).thenReturn(val2);
when(ex3.doit(val2)).thenReturn(result);

Myclass myClass=new MyClass(ex1,ex2,ex3);

assertEquals(result,myClass.doSomething(arg1))
}

The test code of this method seems to be a simple repetition of the code itself, but more complex. Does the test of this kind of class whose role is to control other classes brings any value?

Thank's

  • 3
    That doesn't look like a proper unit test of doSomething to me. A proper unit test of doSomething would simply call doSomething with some test value and assert the expected return result. – Robert Harvey Mar 30 '17 at 19:05
  • 3
    @RobertHarvey But an unit test tests an unit in isolation, without collaborator objects. Is it then not correct to mock these collaborators so that our tests only exercise the unit we want to test, not any other objects that can be verified independently? – amon Mar 31 '17 at 5:47
  • 1
    @amon: You can call the test whatever you want. Mocking all of this classes' internals doesn't seem like a particularly useful exercise to me; presumably, when you new up the class, it already instantiates the objects that it needs. I'm not particularly concerned with "correctness;" the metric should be "is this test useful in determining whether or not the software behaves properly?" – Robert Harvey Mar 31 '17 at 16:34
5

Does it bring any value?

Yes - it is testing interaction.

If you have already unit tested the functionality of the three methods being called, then they are guaranteed to perform correctly. This kind of test is a State Test; call the method and check you receive the correct result.

But here you want to perform an Interaction Test; one that tests that some methods were called correctly (you may not know what these methods do to the data, so you cannot test the result's value consistently).

To perform an interaction test, change your mocks to add an incrementor to each method, then assert that the incremented variables are what you expected:

How you implement the incrementor will depend on how you have created your classes and how you are mocking.

As a brief seudo example:

@Test
void doSomething()
{
    ExternalClass1 ex1=mock(ExternalClass1);
    ExternalClass2 ex2=mock(ExternalClass2);
    ExternalClass3 ex3=mock(ExternalClass2);

    int ex1Count = 0;
    int ex2Count = 0;
    int ex3Count = 0;

    when(ex1.invoke(arg1)).do(ex1Count++).thenReturn(val1);
    when(ex2.call(val1)).do(ex2Count++).thenReturn(val2);
    when(ex3.doit(val2)).do(ex3Count++).thenReturn(result);

    Myclass myClass = new MyClass(ex1,ex2,ex3);

    myClass.doSomething(arg1);

    assertEquals(1, ex1Count);
    assertEquals(1, ex2Count);
    assertEquals(1, ex3Count);

    // assertEquals(2, ex1Count); // If for example ex3 utilises ex1 once (and you know that it should)...
}
  • 2
    The mocking framework Moq provides functionality to check that functions were called, how often they were called, and with the correct parameters. That makes such interaction tests more useful. – Bernhard Hiller Mar 31 '17 at 9:53
  • Perfect! I think a few more will do this out of the box. Others may need a method injecting into the called object. – Steve Padmore Mar 31 '17 at 10:55
2

The only value it brings is code coverage, which means you have coverage on doSometing() method. Is that valuable? Maybe on a management report, but as far as testing/validating behavior it is worthless.

The doSometing() method is a wrapper or façade. It doesn't contain any logic other than call outs to other methods, so it doesn't need a test. Other tests on ExternalClass1, ExternalClass2, and ExternalClass3 will cover actual functionality.

1

Nope! Not as-is.

You either need much more depth, or far less—depending greatly on the particular unit in question. As it stands, your test validates only that doSomething() returns the return value of ex3.doit().

So ...

If you care how doSomething() does its work... It's not necessarily atypical to test a an adapter, facade, or any sort of wrapper in this manner. But, you need to lock this implementation down, and your test falls woefully short. It doesn't actually ensure that the wrapper manages the whole process correctly.

To achieve that level of assurance, you need to also validate that each internal method is invoked when expected, as expected.

However, this is a rare need, in my experience. I've only really had to validate internal implementation details once ... Building an API request logger: I needed to ensure that the logger managed an underlying HTTP connection in a very particular way. And, I wasn't at liberty to perform a full integration test... because I'd be slamming a production API (outside my control) with test data.

In that case, I actually built full fakes of the HTTP classes I'd be using in production. I logged every little interaction between my logger and the resource it was managing. And, I had a bunch of tests that asserted on the details of those interactions.

But, in most cases, your tests shouldn't care how doSomething() works. They should just care that the effects and the results are correct.

So, if you don't care how doSomething() actually does its work... just write an integration test.

An "integration test" is still, in my opinion, a "unit test." The unit just happens to be "large."

1

Your functional test may need to call doSomething and test its being in reasonable order by that. Your unit tests likely already check every part that it invokes.

One thing your unit test can test for doSomething is its error handling. If any of the three calls may fail for certain inputs, and you have certain expectations about exceptions being (or not) raised for such invalid inputs, it may be worth testing. Likely the clients of doSomething depend on that, and don't want to depend on its implementation details.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.