0

I have a service class that extends another service with a constructor. This class has an autowired field and a method that I wanted to unit test using Mockito. However, I am having trouble writing a unit for it.

Let say the service looks somewhat like this:

@Service
public class SomeService extends Service {

    @Autowired
    private SomeClient someClient;

    public SomeService(Product product, @Qualifier(Constants.SOME_BEAN) Details temp) {
        super(product, temp);
    }
    @Override
    public State create() {
        Request request = new Request();
        request.set...
        request.set..

        Status status = someClient.createTenant(request);
        ..
        .. // construct a State object and return
    }
}

Now, I am writing a test for this class and I am trying to unit test the method create() above. I am having trouble mocking someClient there when this method is called by my test.

The test class looks somewhat like:

@RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
    private Detail temp;

    private SomeFacade service;

    private SomeClient someClient;

    private Product product;

    @Before
    public void setup() {
        temp = mock(Details.class);
        product = mock(Product.class);
        service = spy(new SomeService(product, temp));
        someClient = mock(SomeClient.class);
    }

    @Test
    public void test() {
        Status status = new Status();
        status.setStatus(...);
        when(someClient.createTenant(any())).thenReturn(status);
        State state =  service.create();

        // asserts
    }
}   

What I want to is that when I call service.create in the test above, the call someClient.createTenant(request); in the method tested should be mocked to return a value and this is something I am not able to get working. Any help?

user1892775
  • 2,001
  • 6
  • 37
  • 58
  • 2
    You need to provide the mock of `someClient` to `SomeService` - either manually by passing the reference or by using Annotations. But Annotations are out of the question for now as your constructor does not have a `SomeService` parameter. – second Aug 03 '19 at 20:23
  • 2
    Don't `@Autowired` on properties, [`@Autowired` on the constructor.](https://www.baeldung.com/constructor-injection-in-spring) Also, you can use Mockito's [`@InjectMocks`](https://static.javadoc.io/org.mockito/mockito-core/3.0.0/org/mockito/InjectMocks.html) to inject the mocks into your unit under test. Furthermore, define what type you expect to be passed: `when(someClient.createTennant(any(Request.class)))...`. – Turing85 Aug 03 '19 at 20:23
  • Another question would by why you create a `spy` on the class under test, but as your example does not cover that part it could be considered off-topic. – second Aug 03 '19 at 20:33
  • Curious why not explicitly inject `SomeClient` into you derived service? – Nkosi Aug 03 '19 at 20:35
  • @Nkosi because the code - as-is - does not allow direct injection of `SomeClient` :-) – Turing85 Aug 03 '19 at 20:36
  • I more meant why not refractor to exicitly inject it. I wasn't clear enough. – Nkosi Aug 03 '19 at 20:39
  • @Nkosi Could you explain more with some code? – user1892775 Aug 03 '19 at 20:40
  • It was already explained in the provided answer. Constructor injection. – Nkosi Aug 03 '19 at 20:41

1 Answers1

4

We can use Mockito's @InjectMocks-annotation to inject mocks into our unit under test. For this, we also need to

  • remove mock intialization from the setup()-method and
  • annotate the mocks with @Mock

Remarks:

  • Instead of field injection, I would recommend constructor injection. This allows, among other things, to keep the structure of the code and create the unit under test within setup() by manual injection
  • I would also recommend to add a type to the when: when(someClient.createTennant(any(Request.class)))...
  • As was pointed out by @second, the spy on the unit under test is superfluous.
  • Also pointed out by @second was the fact that field injection will not work if a constructor is present
Turing85
  • 18,217
  • 7
  • 33
  • 58
  • 2
    Note that field injection won't work as long as a construtor is present (see my answer here: https://stackoverflow.com/questions/57091389/injectmocks-gives-me-null-when-i-combine-constructor-injectionchild-class-and/57093368#57093368) – second Aug 03 '19 at 20:36
  • `... the spy on the unit under test is superfluous.` -- I recently had a discussion with some people who consider that `bad smell`. There can be reasons for that however they are not mentioned in the example. – second Aug 03 '19 at 20:43
  • @second let's continue this discussion in [chat] – Turing85 Aug 03 '19 at 20:48
  • I think I need to understand this better. At this time, I cannot really change the constructor. Can I still unit test and mock that call? Some code for my example will help understand better, if possible – user1892775 Aug 03 '19 at 20:54
  • 1
    @user1892775 I linked the mockito documentation. It includes an example. I also wrote a two-point list for what to do. I will not refactor the test for you, sorry. – Turing85 Aug 03 '19 at 20:55
  • Upvote for recommendation of constructor injection and for typing the call to `any` – Gavin Aug 03 '19 at 22:22