1

I'm having trouble with Mockito because I can't mock a property of a third class when I'm testing another one.

The class I'm testing:

@Component
public class MyTransformer {
    @Autowired
    private MyService myService;
    
    public Output myMethod(Context context, Input input, Something something) throws Exception {
        ServiceInput serviceInput = createServiceInput(something);
        myService.doSomething(context, input, serviceInput);
        return new Output(); 
    }
}

The interface used in the previous class:

public interface MyService {
    public void doSomething(Context context, Input input, Something something) throws Exception;
}

The real implementation of the service:

@Service
public class MyServiceImpl implements MyService {

    @CustomServiceAnnotation(serviceName = "MyServiceName", apiPaths = {
        ServiceApiPath.class
    })
    private GatewayProxyInvoker gatewayProxyInvoker; // this property is null

    @Override
    public void doSomething(Context context, Input input, Something something) throws Exception {
        Request request = MyServiceCommand.createRequest(context, input, something);
        Response response = Invoker.invoke(Response.class, gatewayProxyInvoker, context, request);
    }
}

The class that throws a NullPointerException because the gatewayProxyInvoker property is null:

public class Invoker {
    public static T invoke(final Class<T> responseType, final GatewayProxyInvoker gatewayProxyInvoker, final Context context, S request) throws Exception {
        return gatewayProxyInvoker.invoke(responseType, context, request);
    }
}

Test class:

@RunWith(MockitoJUnitRunner.class)
public class MyTransformerTest {
    @InjectMocks
    private MyTransformer transformer;

    @Mock
    private MyServiceImpl myService;
    
    @Mock
    private GatewayProxyInvoker gatewayProxyInvoker;
    
    @Test
    public void myMethodTest() throws Exception {
        Response myResponse = new Response();
        
        doCallRealMethod().when(myService).doSomething(any(Context.class), any(Input.class), any(Something.class));
        
        when(gatewayProxyInvoker.invoke(any(Class.class), any(Context.class), any(Request.class))).thenReturn(myResponse);
        
        transformer.myMethod(/*valid input params*/);
    }
}

The property "gatewayProxyInvoker" is null so I'm thinking I'm doing something wrong in the process of mocking it.

The code works fine, it's my JUnit test that is not working.

How can I mock a property of a third class when I'm testing another one? In my example as you can see the method is void, the class I'm testing use an interface.

Thank you all, guys!

Ex.
  • 57
  • 7
  • Can you change `MyServiceImpl` so that it receives `GatewayProxyInvoker gatewayProxyInvoker` as a constructor parameter? – João Dias Nov 02 '21 at 17:59
  • Hello @JoãoDias, unfortunately I can't change the code, I need to work on the specific Junit test. – Ex. Nov 02 '21 at 18:02
  • You are injecting your mocks in the `transformer` while `myService` is defined as a mock itself, hence the null field when you are trying to call its actual method instead of specifying its mocked behaviour. If you are trying to test the `transformer` (as it seems you are), then you only need to mock its direct "dependency", aka the service it is interacting with, define a behaviour for the mock service, and disregard the `gatewayProxyInvoker` which isn't really needed. – Morfic Nov 02 '21 at 18:04
  • Good point, that is true indeed. – João Dias Nov 02 '21 at 18:05
  • Also, no offense, but your current code makes little sense. How/where is the transformer output created? What happens to the response created in the service? Is there any connection between the 2? – Morfic Nov 02 '21 at 18:08
  • I'm trying to test the whole flow starting from the `transformer`, the need arises for business reasons. I already know how to test classes individually and it's not what I need. The code shown is only an example, I don't need code review except for the junit test. – Ex. Nov 02 '21 at 18:14
  • But then you need an integration test and mocks should be avoided. – João Dias Nov 02 '21 at 18:16
  • 1
    Sorry if I offended you, not my intention. You mentioned mocks which usually indicate unit tests, hence my initial reply. If you think only your code is messy and requires review, which you gracefully rejected even if it wasn't one, then you should see how ugly mine is sometimes. However there is a difference, I thought about mine, I wrote it, I know the context, but I know little about yours, except what you posted, hence my second comment when I had doubts. So I'd say both revealed you want an integration test as @JoãoDias said. Lastly, we're trying to help, not make fun of you or your code. – Morfic Nov 02 '21 at 19:12

1 Answers1

0

I've found a solution here: Mockito Inject mock into Spy object

This example works well:

@RunWith(MockitoJUnitRunner.class)
public class MyTransformerTest {
    @InjectMocks
    private MyTransformer transformer;

    @InjectMocks
    private MyServiceImpl myService;
    
    @Mock
    private GatewayProxyInvoker gatewayProxyInvoker;
    
    @Before
    public void init() {
        myService = Mockito.spy(new MyServiceImpl());
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void myMethodTest() throws Exception {
        Response myResponse = new Response();
        when(gatewayProxyInvoker.invoke(any(Class.class), any(Context.class), any(Request.class))).thenReturn(myResponse);
        transformer.myMethod(/*valid input params*/);
    }
}
Ex.
  • 57
  • 7