简体   繁体   中英

Testing custom predicate using mocked Exception failing due to incorrect Mock class

I have created a custom Predicate below and want to test it using mockito . I am creating the mocks of the specific exception classes since these dont have public constructor. After running the test assert is failing since the predicate is returning false instead of true . On printing the class of the mocked exception it has WebClientResponseException$ServiceUnavailable$MockitoMock$54675 .Seems like the mock is not recognized correctly. Am I doing something wrong here?

PredicateTest

@ExtendsWith(MockitoExtention.class)
class PredicateTest{

@InjectMocks
CustomPredicate customPredicate;



@Test
public void testPredicate(){

final ServiceUnavailable serviceUnavailable = mock(ServiceUnAvailable.class);

assertTrue(customPredicate.test(serviceUnavailable))
    
}  
}

CustomPredicate

CustomPredicate implements Predicate<Throwable>{

private static final List<Class<?>> Exceptions= Arrays.asList(WebClientResponseException.ServiceUnavailable.class);

private static final Predicate<? super Throwable> ClassToControl= throwable -> Exception.contain(throwable.getClass());


@Override
public boolean test(Throwable t){

return ExceptionUtils.getThrowableList(t).stream().anyMatch(ClassToControl);

}


}

Actually the problem is in mock(ServiceUnAvailable.class) - when you create an object this way it will have a class ServiceUnAvailable$MockitoMock , not a ServiceUnAvailable.class , it means that the next your check fill fail:

Predicate<? super Throwable> ClassToControl= throwable -> Exception.contain(throwable.getClass());

Because Exceptions list doesn't contain element ServiceUnAvailable$MockitoMock .


In order to test exceptions this way I would suggest the next fix (I changed the code a little bit, but I hope the idea is clear):

Predicate:

public class CustomPredicate implements Predicate<Throwable> {
    private final List<Class<?>> exceptions;
    private final Predicate<? super Throwable> classToControl;

    public CustomPredicate(Class<?>... exceptions) {
        this(Arrays.asList(exceptions));
    }

    public CustomPredicate(List<Class<?>> exceptions) {
        this.exceptions = exceptions;
        this.classToControl = throwable -> this.exceptions.contains(throwable.getClass());
    }

    @Override
    public boolean test(final Throwable throwable) {
        return ExceptionUtils.getThrowableList(throwable).stream()
            .anyMatch(classToControl);
    }
}

Test:

public class PredicateTest {
    @Test
    public void testPredicate() {
        final IllegalStateException serviceUnavailable = Mockito.mock(IllegalStateException.class);
        CustomPredicate customPredicate = new CustomPredicate(serviceUnavailable.getClass());
        assertTrue(customPredicate.test(serviceUnavailable));
    }
}

@VolodyaLombrozo correctly identified the root cause of the problem:

var serviceUnavailableMock = mock(ServiceUnavailable.class);
System.out.println(serviceUnavailableMock.getClass());
System.out.println(serviceUnavailableMock.getClass() == ServiceUnavailable.class);
System.out.println(serviceUnavailableMock instanceof ServiceUnavailable);

// class org.example.ServiceUnavailable$MockitoMock$X21NGyAU
// false
// true

On top of his answer, I'd like to suggest more options to change your code:

Let's refactor CustomPredicate:

  • test() converts wraps input into 1-element list, converts it to a stream, and runs a test with anyMatch. This is confusing and unnecessary.
public class CustomPredicate implements Predicate<Throwable> {
    private static final List<Class<?>> Exceptions = List.of(ServiceUnavailable.class);

    @Override
    public boolean test(Throwable t) {
        return Exceptions.contains(t.getClass());
    }
}

Fix the test

You have 2 options, depending if you want your predicate to pass on subclasses:

  • pass actual instance of ServiceUnavailable to test() (use new instead of mocking)
  • use instanceof check in test (using stream and anyMatch, but on Exceptions list)

All of this assumes that this is dummy code and your Exceptions list is longer. If you want to test your throwable against one class, a lambda would feel pretty adequate:

Predicate<Throwable> pThrowable1 = t -> t instanceof ServiceUnavailable; // if you care about subclasses
Predicate<Throwable> pThrowable2 = t -> ServiceUnavailable.class.equals(t.getClass()); // if you want strict equality

Update: mockito-inline

If you use mockito-inline , it changes the way the mocks are constructed:

This alternative mock maker which uses a combination of both Java instrumentation API and sub-classing rather than creating a new class to represent a mock.

InlineByteBuddyMockMaker javadoc says:

This mock maker will make a best effort to avoid subclass creation when creating a mock. Otherwise it will use the org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker to create the mock class. That means that the following condition is true

class Foo { } assert mock(Foo.class).getClass() == Foo.class;

unless any of the following conditions is met, in such case the mock maker fall backs to the the creation of a subclass.

  • the type to mock is an abstract class.
  • the mock is set to require additional interfaces.
  • the mock is explicitly set to support serialization

Thus, if you use mockito-inline the test passes, as there is no subclass created:

var serviceUnavailableMock = mock(ServiceUnavailable.class);
System.out.println(serviceUnavailableMock.getClass());
System.out.println(serviceUnavailableMock.getClass() == ServiceUnavailable.class);
System.out.println(serviceUnavailableMock instanceof ServiceUnavailable);

// class org.example.ServiceUnavailable
// true
// true

So the issue was I was not having mockito-inline jar in the pom. Not sure why but this solved the issue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM