Is there a better way to test the following methods without bullying returning mocks?

Assume the following setting:

interface Entity {} interface Context { Result add(Entity entity); } interface Result { Context newContext(); SpecificResult specificResult(); } class Runner { SpecificResult actOn(Entity entity, Context context) { return context.add(entity).specificResult(); } } 

I want to see that the actOn method just adds an object to the context and returns specificResult. The way I'm testing it right now is as follows (using Mockito)

 @Test public void testActOn() { Entity entity = mock(Entity.class); Context context = mock(Context.class); Result result = mock(Result.class); SpecificResult specificResult = mock(SpecificResult.class); when(context.add(entity)).thenReturn(result); when(result.specificResult()).thenReturn(specificResult); Assert.assertTrue(new Runner().actOn(entity,context) == specificResult); } 

However, this seems like an awfully white box, with bullying returning ridicule. What am I doing wrong, and does anyone have a good “best practice” text that they can point me to?

As people requested more context, the original problem is the DFS abstraction, in which Context collects graph elements and calculates the results that are matched and returned. ActOn is actually a leaf action.

+4
source share
6 answers

It depends on what and how much you want your code to be tested. As you noted tdd , I suppose you wrote your test contracts before any actual production code.

So, in your contract, what do you want to verify using the actOn method:

  • So that it returns the SpecificResult value given by both a Context and Entity
  • That add() , specificResult() interactions occur respectively Context and Entity
  • That SpecificResult is the same instance returned by Result
  • and etc.

Depending on what you want to test, you will write the appropriate tests. Perhaps you should consider softening your approach to testing if this section of the code does not matter . And vice versa, if this section can cause the end of the world, as we know it.

Generally speaking, white-box tests are fragile , usually verbose and not expressive , but difficult to refactor . But they are well suited for critical sections, which should not change much and neophytes.

In your case, the layout returned by mock looks like a whitebox test. But then again, if you want to ensure this behavior in production code, that's fine. Mokito can help you with deep stubs.

 Context context = mock(Context.class, RETURNS_DEEP_STUBS); given(context.add(any(Entity.class)).specificResult()).willReturn(someSpecificResult); 

But don't get used to it, as this is usually considered bad practice and a trial smell.

Other comments:

  • Your test method name is not accurate enough testActOn tells the reader what behavior you are testing. Typically, practitioners replace the method name with a contract proposal, such as returns_a_SpecificResult_given_both_a_Context_and_an_Entity , which is more readable and gives the practitioner what is being tested.

  • You create mock instances in a test with the syntax Mockito.mock() , if you have several such tests, I would recommend that you use MockitoJUnitRunner with @Mock annotations, this will slightly stretch your code and allow the reader to better see what is happening in this particular test .

  • Use BDD (Behavior Driven Dev) or AAA (Arrange Act Assert).

For instance:

 @Test public void invoke_add_then_specificResult_on_call_actOn() { // given ... prepare the stubs, the object values here // when ... call your production code // then ... assertions and verifications there } 

In general, as Eric Evans said Context is king , you must make decisions based on this context. But you really should adhere to best practice as much as possible.

There are a lot of tests read here, Martin Fowler has very good articles about this, James Carr made a list to check anti-patterns , there are also many readings on how to use ridicule well (for example, don’t mock types that you don’t own mojo ), Nat Pryce is a co-author of A growing test-driven object-oriented software that, in my opinion, should be read, plus you have google;)

+3
source

Consider using fakes instead of mocks. It’s not really clear what the classes in question mean, but if you can create a simple built-in (not thread safe, not constant, etc.) implementation of both interfaces, you can use it for flexible testing without fragility, sometimes comes from ridicule.

+1
source

I like to use names starting with mock for all my mock objects. Also, I would replace

  when(result.specificResult()).thenReturn(specificResult); Assert.assertTrue(new Runner().actOn(entity,context) == specificResult); 

from

 Runner toTest = new Runner(); toTest.actOn( mockEntity, mockContext ); verify( mockResult ).specificResult(); 

because all you are trying to claim is that specificResult() run on the right layout of the object. While your initial statement does not make it so clear that it is approved. This way you don't need a layout for SpecificResult . This reduces you to a single when call, which seems to me to be suitable for such a test.

But yes, it looks like an awfully white box. Is Runner public class or some hidden part of a higher-level process implementation? If this is the last, then you probably want to write tests around behavior at a higher level; rather than investigate implementation details.

+1
source

Unaware of the context of the code, I would suggest that Context and Result most likely simple data objects with very little behavior. You can use Fake, as suggested in another answer, or, if you have access to implementations of these interfaces, and the design is simple, I would just use real objects instead of fakes or Mocks.

0
source

Although the context will provide more information, I myself do not see any problems with your testing methodology. The whole point of fraudulent objects is to verify the behavior of the call without the need to instantiate implementations. I just don't need to create stub objects or use real implementation classes.

However, this seems like an awfully white box, with bullying returning ridicule.

It could be more about class design than in testing. If this is how the Runner class works with external interfaces, I see no problem with the test simulating this behavior.

0
source

Firstly, since no one mentioned this, Mockito supports the chain, so you can just do:

 when(context.add(entity).specificResult()).thenReturn(specificResult); 

(and see Bryce's comment on how to do this, sorry, I skipped this!)

Secondly, it contains a warning: "Do not do this except legacy code." You are correct that the layout-returning-layout was a little strange. It’s okay to make “white boxes” mocking, because you really say, “My class should collaborate with an assistant, like <this>,” but in this case, it collaborates with two different classes, linking them together.

It’s not clear why Runner should get a SpecificResult, unlike any other result obtained from context.add(entity) , so I’m going to make an assumption: Result contains a result with some messages or other information, and you just want to know whether success or failure .

I like it when I say: "Don't tell me everything about my order, just tell me that I did it successfully!" The runner should not know that you need only this particular result; it should just return everything that came out, just like Amazon shows you your total, postage and everything that you bought, even if you caught a lot there and know perfectly well what you get.

If some classes regularly use your Runner only to get a specific result, while others require more feedback, I would do two methods for this, maybe call something like add and addWithFeedback , just like Amazon let you do one-click purchases on another route.

However, be pragmatic. If it reads the way you did it and everyone understands it, use Mockito to tie them up and call it day. You can change it later if you need.

0
source

Source: https://habr.com/ru/post/1394884/


All Articles