1

Let's say I have a constructor:

public Something(A a) {
    this.a = a;
    this.b = a.someLogicMethod();
    this.c = b.someLogicMethod();
}
@Autowired
private Class d;

Is this code testable? If not, What should I change to make it testable and keep the initialization of b to be through method of object a, which i get as an argument to the constructor of my class.

I want to pass my object (that I created in the test class) of classB to the Something class, The problem is that Something should be mocked and the object a is mocked, so I can't change the behaviour of a with when&thenReturn so when a.someLogicMethod is called, it will return my object of classB that I created in the test class, because when Something is mocked, the when&thenReturn logic will be called after a.someLogicMethod() has already been called and b is set. I also can't have a setter for the b object because it should be initialized with a.someLogicMethod(). The reason i need Something to be mocked is because I want that the object d will be mocked.

Thanks.

T.S
  • 911
  • 8
  • 24
  • 2
    Yes, it's testable. How about trying to test it? Wouldn't that answer your question? Or at least make it more useful to you? – JB Nizet Nov 17 '17 at 07:54
  • @JBNizet Actually I did, the problem is that I want the a object to be mocked and that's a problem for b. what do you think? – T.S Nov 17 '17 at 08:01
  • I don't see any problem mocking the A object. Do it, and if you have a concrete problem, explain it, with the relevant code. – JB Nizet Nov 17 '17 at 08:05
  • @GhostCat , If I could I would delete this question because I don't remember what I wanted to do and what exactly is the problem. When I read this question now, I don't understand my own question because if I wanted to use 'Something' class as Mock to test another class, I shouldn't care about a,b,c,d, because a,b,c,d are probably fields that are used in the methods of the 'Something' class and if I mock this class, then I can stub every method so I don't need this fields. – T.S Sep 14 '18 at 08:31
  • ;-) Lets reward your honesty. The alternative would be that you upvote the answer, accept one, and everybody forgets about it ;-) – GhostCat Sep 14 '18 at 09:19
  • I don't know which answer you want me to accept, the answer that should be accepted is that I shouldn't care about any of the 'Something' class fields and how they are initialized because those fields are used in the methods of the class and as I said, if I choose to mock this class then I can mock the responses of the methods so I don't actually care about those fields. No reward is needed, everyday I learn new things as a developer and I'm sure I asked a lot of stupid questions in the past and probably I'll ask new stupid questions in the future :) – T.S Sep 14 '18 at 13:34

2 Answers2

1

You are injecting A into Something so just mock A and then let A return a mocked B when someLogicMethod() is called.

For example:

A mockA = Mockito.mock(A.class);
B mockB = Mockito.mock(B.class);
Mockito.when(a.someLogicMethod()).thenReturn(mockB);

Something something = new Something(mockA);

Then you can invoke on something in your test case and so you can control the behaviour of something by setting expectations on mockB using when/then or when/throw or doThrow/when etc.

glytching
  • 44,936
  • 9
  • 114
  • 120
  • That's what i wanted but in that that case you initialize Something with the new keyword and I want something to be mocked also so that's the problem, I can't use when, then return, because something should also be mocked. – T.S Nov 17 '17 at 08:15
  • If `Something` is to be mocked then you won't care about mocking `A` or `B` since anything they _might_ have done will be covered by expectations set on your `mockSomething`. At this stage I really think it's best to follow the advice of @JBNizet and provide us with a [clear statement](https://stackoverflow.com/help/mcve) of what you are trying to do, otherwise all we can do is guess. – glytching Nov 17 '17 at 08:18
  • 1
    Sorry, T.S., but that makes absolutely no sense. If you want to TEST `Something`, then you cannot MOCK `Something`, since then you would only test the mock of `Something`, which is pretty useless - we normally can agree that Mockito works just fine, thanks. You only need to mock `Something`, if you want to test another class that has a `Something` dependency. But if that's the case, then you didn't show us the actual code to test nor the real problem. In general, mocking `Something` should be no problem, since Mockito should be able to handle that constructor just fine. – Florian Schaetz Nov 17 '17 at 08:20
  • @FlorianSchaetz I'm sorry, i edited my question and tried to better explain the problem, anyway, I think my question is too complicated. – T.S Nov 17 '17 at 08:32
  • @T.S I suspect this is solveable but your question still isn't clear enough. Rather than trying to explain it _in words_ why not post some code? Maybe you could provide your **actual** test case? – glytching Nov 17 '17 at 09:01
  • You do not need to mock `Something` for d to be mocked. You can simply use @Mock and @InjectMocks to create mocks of A and D and have them injected into Something. – Florian Schaetz Nov 17 '17 at 09:02
  • @FlorianSchaetz, but in that case I can't change the behaviour of the mocked object a when someLogicMethod is called. If I do what you say then object of something will be initialized before i do when&thenReturn to a when calling someLogicMethod. – T.S Nov 17 '17 at 09:07
  • @glytching The code is too complicated and include too many classes that related to the problem, I tried to simplify the problem, anyway thanks trying to help, I will read more about mockito and try to think of a solution. – T.S Nov 17 '17 at 09:09
  • Honestly? Easiest solution is simply doing it by hand. Since you are using Spring, using ReflectionTestUtils to inject d, for example. – Florian Schaetz Nov 17 '17 at 09:15
  • @FlorianSchaetz is it recommended? good or bad practice? because now that you say that it makes me want to do setField for all the fields of the Something class and it will be much more convenient and easy. what you think? Or should I do this only for fields with the spring annotations like Autowired annotation. – T.S Nov 17 '17 at 09:29
  • `@InjectMocks` does basically the same. Of course, if you want to talk about "recommended", then it's actually recommended to use constructor injection instead of field injection. This way you could simply `@Mock` your dependencies and then create a new Something instance in your `@Before` method (or in the `@Test` method) by simply calling the constructor with all the mocked dependencies. – Florian Schaetz Nov 17 '17 at 09:34
  • I agree with the constructor injection, i think I will just change that and that's it. thank you for your time. – T.S Nov 17 '17 at 09:42
0

There are two aspects here:

  • your constructor is test-friendly, because you pass an instance of A to it. So, as nicely outlined by the other answer you should be able to pass a mocked instance to that constructor - resulting in you having full control on what happens for the following method invocations.

  • regarding @autowired: here the answer is to turn to use the special spring JUnit runner (see here for all the glory details)

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • I try the MockBean annotation, the problem is that this make my context dirty so my tests pass in the specific class but other tests failed, i tried to use the DirtyContext annotation but that doesn't seems to work. – T.S Nov 22 '17 at 10:10