简体   繁体   中英

Mocking getClass()

In Java I want to write test for method (simplified snippet):

public class MyClass {
    private static final Set<Class> SOME_SET = new HashSet<Class>(Arrays.asList(Foo.class, Bar.class));

    public boolean isValid(Class clazz){
        return SOME_SET.contains(clazz);
    }
}

The problem with following test

import static org.mockito.Mockito.when;
import org.mockito.Mockito;

public class MyClassTest {
    @Test
    public void isValid_Foo_returnsTrue(){
        Foo foo = Mockito.mock(Foo.class);
        MyClass target = new MyClass();
        assertTrue(target.isValid(foo));
   }
}

is that on mocked class Foo, foo.getClass() returns the class name with additional suffix. Something like this:

Foo$$EnhancerByMockitoWithCGLIB$$45508b12

Because of this reason test fails because SOME_SET.contains(clazz) returns false .

I was unable to mock getClass() method on Foo:

Mockito.when(foo.getClass()).thenReturn(Foo.class);

Because compiler was complaining: The method thenReturn(Class<capture#1-of ? extends Foo>) in the type OngoingStubbing<Class<capture#1-of ? extends Foo>> is not applicable for the arguments (Class<Foo>)

The question is, how to achieve that getClass() method of mocked object returns same value as getClass() method on real(non mocked) object ?

Mockito.mock(Foo.class) results a object in type of Foo, but not exactly Foo class. As at the background it will create an anonymous proxy, which is a subclass of Foo. So, the class isn't the same.

One note to your implementation of isValid is that: do you really want to check the class, or just to check the type (that will also accept subclass, isAssignableFrom) of the class. If you check for type matching, then you can test your method with mocked class.

Also, do you think that you can somehow work on objects rather than classes (eg isValid(object))? Using object is a better approach in OOP than clazz in my perspective. I would suggest:

    public boolean isValid(Object obj) {
        return SOME_SET.stream().anyMatch(clazz -> clazz.isInstance(obj));
    }

I see no sense on mocking Foo on your scenario. Just pass Foo.class as param. You are checking MyClass.isValid logic, and you are not gonna check any interaction on Foo.

Simple: you can't.

Keep in mind: a mocked object is not really of the mocked class. It is something that the mocking framework creates for you to look like the real class. But you can't have it both ways: either something is faked or it is real .

And in your case, a reasonable test looks different anyway:

@Test
public void testIsValidForInvalidClass() {
  assertThat(target.isValid(String.class), is(false));
}

@Test
public void testIsValidForValidClass() {
  assertThat(target.isValid(Foo.class), is(true));
}

for example. There is no point in mocking Class objects in the first place. As you can easily construct and pass real objects of class Class!

When initializing the object 'foo', just create an object of type Foo instead of creating a mock of Foo. For example, if possible:

Foo foo = new Foo()

This is possible with a, currently incubating, alternative mock maker implementation. See here how to enable it. Calling getClass on such mocks returns the original class so equals will work as expected.

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