Search code examples
junitmockitojunit4junit5powermockito

Mock a void method


//Original method:

@Autowired
    private ConversionServiceValidator validator; 

    public CRSConversionResult convertCRS(ConvertCrsVo convertCrsVo) throws Exception {

if (validator.isSameSourceAndTarget(convertCrsVo))
            throw new ValidationException(Constants.BADREQUEST);

        if (convertCrsVo.getPreferredTransforms() != null) {
            List<TransformVo> preferredTransformList = new ArrayList<>();
            for (TransformVo transformVo : convertCrsVo.getPreferredTransforms()) {
                preferredTransformList.add(getPerfByCode(transformVo));
            }
            convertCrsVo.setPreferredTransforms(preferredTransformList);
        }
        convertCrsVo.setSourceCRS(getCrsVoByCode(convertCrsVo.getSourceCRS()));
        convertCrsVo.setTargetCRS(getCrsVoByCode(convertCrsVo.getTargetCRS()));
        convertCrsVo = validator.replaceCoordinates(convertCrsVo);
        logger.info("ShellGeodeticService::convertCRS::Request to GeoCalService convertpoints::" + mapper.writeValueAsString(convertCrsVo));
        ConvertPointsResponse response = geoCalService.convertCRS(convertCrsVo);
        CRSConversionResult result = new CRSConversionResult();
        result.setCriteriaMessage(response.getCriteriaMessage());
        result.setResultPoints(response.getResultPoints());
        result.setTransformName(response.getTransformName());
        result.setTransformDescription(response.getTransformDescription());
        // added schema as per pbi 195298
        List<ConvertedTransformsResult> transformsResults = new ArrayList<>();
        if (response.getTransforms() != null || !response.getTransforms().isEmpty())
            response.getTransforms().stream().forEach(
                    t -> transformsResults.add(new ConvertedTransformsResult().getConvertedTransformsResult(t)));
        result.setTransforms(transformsResults);
        String logmessage=generateLogMessage(result,convertCrsVo);
        logger.info(logmessage);
        validator.isResponseValid(result);
        return result;
}

//The testcase for the above method

@Test
    public void testconvertCRSJob() throws Exception{
        ConvertCrsVo convertCrsVo = TestDataFactory.getConvertCrsVo();
        CRSConversionResult crsConversionResult = TestDataFactory.getCRSConversionResult();
        ConversionServiceValidator conversionServiceValidatorMock = mock(ConversionServiceValidator.class);
Mockito.when(geoCalService.convertCRS(Mockito.any()))
        .thenReturn(TestDataFactory.getConvertPointsResponse(convertCrsVo));
Mockito.when(validator.replaceCoordinates(convertCrsVo))
        .thenReturn(TestDataFactory.getConvertCrsVo());
Mockito.when(geoCalService.search(Mockito.any(SearchFilter.class)))
        .thenReturn(TestDataFactory.getSearchResultResponseForCRS());
Mockito.when(shellGeodeticService.convertCRS(convertCrsVo))
        .thenReturn(TestDataFactory.getCRSConversionResult());  
shellGeodeticService.convertCRSJob();

        }

The error that i am getting is as below:

org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue: 'isResponseValid' is a void method and it cannot be stubbed with a return value! Voids are usually stubbed with Throwables: doThrow(exception).when(mock).someVoidMethod();


If you're unsure why you're getting above error read on. Due to the nature of the syntax above problem might occur because: 1. The method you are trying to stub is overloaded. Make sure you are calling the right overloaded version. 2. Somewhere in your test you are stubbing final methods. Sorry, Mockito does not verify/stub final methods. 3. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method. 4. Mocking methods declared on non-public parent classes is not supported.

at com.shell.geodetic.GeodeticConvertionApiAppTests.testconvertCRSJob(GeodeticConvertionApiAppTests.java:1783)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

Can someone help me on how to stub the void method "isResponseValid" ? I tried around 100 combinations that i saw in SOF and nothing worked. Thanks for the help in advance.

*Edit

Class ConversionServiceValidator {

    public void isResponseValid(CRSConversionResult response) throws InvalidDataException {

            if (response.getResultPoints().isEmpty() || response.getResultPoints() == null) {
                throw new ValidationException("Request body has incorrect format");
            } else {
                for (Point point : response.getResultPoints()) {
                    if (point.getX().trim().equals("0") || point.getY().trim().equals("0")) {
                        throw new InvalidDataException(400, "Bad Request", "WARNING: Not all points could be converted",
                                response);
                    }
                }
}

Solution

  • It is a mock @InjectMocks ShellGeodeticService shellGeodeticService;

    shellGeodeticService is not a mock. @InjectMocks is used for the class under test, where the mocks are injected into.

    That implies you can not use

    Mockito.when(shellGeodeticService.convertCRS(convertCrsVo)) 
           .thenReturn(TestDataFactory.getCRSConversionResult());
    

    in your test as only mocks(or spys) can be used within Mockito.when.


    Actually im trying to run test case for shellGeodeticService.convertCRS() and since it calls isResponseValid method internally , i have to mock that also right?

    No, that is incorrect. If validator is a mock every method invocation will do nothing by default. So, unless you want to throw an exception, you do not need to define anything.


    As your question lacks some details, I assume a complete version of your test could be similiar to this:

    @InjectMocks
    ShellGeodeticService shellGeodeticService;
    
    @Mock
    ConversionServiceValidator validator;
    
    @Mock
    ... geoCalService; // some unknown class
    
    @Test
    public void testconvertCRSJob() throws Exception{
    
        ConvertCrsVo convertCrsVo = TestDataFactory.getConvertCrsVo();
    
        // Note sure whether this is correct by your logic as there is no `replacement` happening.
        Mockito.when(validator.replaceCoordinates(convertCrsVo)).thenReturn(convertCrsVo);
    
        Mockito.when(geoCalService.convertCRS(Mockito.any())).thenReturn(TestDataFactory.getConvertPointsResponse(convertCrsVo));
    
        CRSConversionResult result = shellGeodeticService.convertCRS();
    
        // do some assertions on the result
    }
    

    As validator is a mock:

    • validator.isSameSourceAndTarget(convertCrsVo) returns false be default
    • validator.isResponseValid( ... ) does nothing by default

    As you did not add the methods getCrsVoByCode, getPerfByCode and generateLogMessage take note that if there are any further interactions with the mocked objects you'll need to add them. (eg.: a call to geoCalService.search is not visible in your test code, so I removed the behaviour definition from the test displayed above)