InlineByteBuddyMockMakerTest.java revision 2637d96c202372854a7c71466ddcc6e90fc4fc53
1package org.mockito.internal.creation.bytebuddy;
2
3import net.bytebuddy.ByteBuddy;
4import net.bytebuddy.ClassFileVersion;
5import net.bytebuddy.description.modifier.Visibility;
6import net.bytebuddy.description.type.TypeDescription;
7import net.bytebuddy.implementation.StubMethod;
8import org.junit.Test;
9import org.mockito.exceptions.base.MockitoException;
10import org.mockito.internal.creation.MockSettingsImpl;
11import org.mockito.internal.creation.settings.CreationSettings;
12import org.mockito.internal.handler.MockHandlerImpl;
13import org.mockito.internal.stubbing.answers.Returns;
14import org.mockito.internal.util.collections.Sets;
15import org.mockito.mock.MockCreationSettings;
16import org.mockito.mock.SerializableMode;
17import org.mockito.plugins.MockMaker;
18
19import java.util.HashMap;
20import java.util.List;
21import java.util.Observable;
22import java.util.Observer;
23import java.util.regex.Pattern;
24
25import static net.bytebuddy.ClassFileVersion.JAVA_V8;
26import static net.bytebuddy.ClassFileVersion.JAVA_V9;
27import static net.bytebuddy.matcher.ElementMatchers.named;
28import static org.assertj.core.api.Assertions.assertThat;
29import static org.assertj.core.api.Assertions.fail;
30import static org.junit.Assume.assumeTrue;
31
32public class InlineByteBuddyMockMakerTest extends AbstractByteBuddyMockMakerTest<InlineByteBuddyMockMaker> {
33
34    public InlineByteBuddyMockMakerTest() {
35        super(new InlineByteBuddyMockMaker());
36    }
37
38    @Override
39    protected Class<?> mockTypeOf(Class<?> type) {
40        return type;
41    }
42
43    @Test
44    public void should_create_mock_from_final_class() throws Exception {
45        MockCreationSettings<FinalClass> settings = settingsFor(FinalClass.class);
46        FinalClass proxy = mockMaker.createMock(settings, new MockHandlerImpl<FinalClass>(settings));
47        assertThat(proxy.foo()).isEqualTo("bar");
48    }
49
50    @Test
51    public void should_create_mock_from_final_class_in_the_JDK() throws Exception {
52        assumeTrue(ClassFileVersion.ofThisVm().isLessThan(JAVA_V9)); // Change when ByteBuddy has ASM6 - see #788
53
54        MockCreationSettings<Pattern> settings = settingsFor(Pattern.class);
55        Pattern proxy = mockMaker.createMock(settings, new MockHandlerImpl<Pattern>(settings));
56        assertThat(proxy.pattern()).isEqualTo("bar");
57    }
58
59    @Test
60    public void should_create_mock_from_abstract_class_with_final_method() throws Exception {
61        MockCreationSettings<FinalMethodAbstractType> settings = settingsFor(FinalMethodAbstractType.class);
62        FinalMethodAbstractType proxy = mockMaker.createMock(settings, new MockHandlerImpl<FinalMethodAbstractType>(settings));
63        assertThat(proxy.foo()).isEqualTo("bar");
64        assertThat(proxy.bar()).isEqualTo("bar");
65    }
66
67    @Test
68    public void should_create_mock_from_final_class_with_interface_methods() throws Exception {
69        MockCreationSettings<FinalMethod> settings = settingsFor(FinalMethod.class, SampleInterface.class);
70        FinalMethod proxy = mockMaker.createMock(settings, new MockHandlerImpl<FinalMethod>(settings));
71        assertThat(proxy.foo()).isEqualTo("bar");
72        assertThat(((SampleInterface) proxy).bar()).isEqualTo("bar");
73    }
74
75    @Test
76    public void should_create_mock_from_hashmap() throws Exception {
77        MockCreationSettings<HashMap> settings = settingsFor(HashMap.class);
78        HashMap proxy = mockMaker.createMock(settings, new MockHandlerImpl<HashMap>(settings));
79        assertThat(proxy.get(null)).isEqualTo("bar");
80    }
81
82    @Test
83    @SuppressWarnings("unchecked")
84    public void should_throw_exception_redefining_unmodifiable_class() {
85        MockCreationSettings settings = settingsFor(int.class);
86        try {
87            mockMaker.createMock(settings, new MockHandlerImpl(settings));
88            fail("Expected a MockitoException");
89        } catch (MockitoException e) {
90            e.printStackTrace();
91            assertThat(e).hasMessageContaining("Could not modify all classes");
92        }
93    }
94
95    @Test
96    @SuppressWarnings("unchecked")
97    public void should_throw_exception_redefining_array() {
98        int[] array = new int[5];
99        MockCreationSettings<? extends int[]> settings = settingsFor(array.getClass());
100        try {
101            mockMaker.createMock(settings, new MockHandlerImpl(settings));
102            fail("Expected a MockitoException");
103        } catch (MockitoException e) {
104            assertThat(e).hasMessageContaining("Arrays cannot be mocked");
105        }
106    }
107
108    @Test
109    public void should_create_mock_from_enum() throws Exception {
110        MockCreationSettings<EnumClass> settings = settingsFor(EnumClass.class);
111        EnumClass proxy = mockMaker.createMock(settings, new MockHandlerImpl<EnumClass>(settings));
112        assertThat(proxy.foo()).isEqualTo("bar");
113    }
114
115    @Test
116    public void should_fail_at_creating_a_mock_of_a_final_class_with_explicit_serialization() throws Exception {
117        MockCreationSettings<FinalClass> settings = new CreationSettings<FinalClass>()
118                .setTypeToMock(FinalClass.class)
119                .setSerializableMode(SerializableMode.BASIC);
120
121        try {
122            mockMaker.createMock(settings, new MockHandlerImpl<FinalClass>(settings));
123            fail("Expected a MockitoException");
124        } catch (MockitoException e) {
125            assertThat(e)
126                    .hasMessageContaining("Unsupported settings")
127                    .hasMessageContaining("serialization")
128                    .hasMessageContaining("extra interfaces");
129        }
130    }
131
132    @Test
133    public void should_fail_at_creating_a_mock_of_a_final_class_with_extra_interfaces() throws Exception {
134        MockCreationSettings<FinalClass> settings = new CreationSettings<FinalClass>()
135                .setTypeToMock(FinalClass.class)
136                .setExtraInterfaces(Sets.<Class<?>>newSet(List.class));
137
138        try {
139            mockMaker.createMock(settings, new MockHandlerImpl<FinalClass>(settings));
140            fail("Expected a MockitoException");
141        } catch (MockitoException e) {
142            assertThat(e)
143                    .hasMessageContaining("Unsupported settings")
144                    .hasMessageContaining("serialization")
145                    .hasMessageContaining("extra interfaces");
146        }
147    }
148
149    @Test
150    public void should_remove_recursive_self_call_from_stack_trace() throws Exception {
151        StackTraceElement[] stack = new StackTraceElement[]{
152                new StackTraceElement("foo", "", "", -1),
153                new StackTraceElement(SampleInterface.class.getName(), "", "", -1),
154                new StackTraceElement("qux", "", "", -1),
155                new StackTraceElement("bar", "", "", -1),
156                new StackTraceElement("baz", "", "", -1)
157        };
158
159        Throwable throwable = new Throwable();
160        throwable.setStackTrace(stack);
161        throwable = InlineByteBuddyMockMaker.hideRecursiveCall(throwable, 2, SampleInterface.class);
162
163        assertThat(throwable.getStackTrace()).isEqualTo(new StackTraceElement[]{
164                new StackTraceElement("foo", "", "", -1),
165                new StackTraceElement("bar", "", "", -1),
166                new StackTraceElement("baz", "", "", -1)
167        });
168    }
169
170    @Test
171    public void should_handle_missing_or_inconsistent_stack_trace() throws Exception {
172        Throwable throwable = new Throwable();
173        throwable.setStackTrace(new StackTraceElement[0]);
174        assertThat(InlineByteBuddyMockMaker.hideRecursiveCall(throwable, 0, SampleInterface.class)).isSameAs(throwable);
175    }
176
177    @Test
178    public void should_provide_reason_for_wrapper_class() {
179        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(Integer.class);
180        assertThat(mockable.nonMockableReason()).isEqualTo("Cannot mock wrapper types, String.class or Class.class");
181    }
182
183    @Test
184    public void should_provide_reason_for_vm_unsupported() {
185        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(int[].class);
186        assertThat(mockable.nonMockableReason()).isEqualTo("VM does not not support modification of given type");
187    }
188
189    @Test
190    public void is_type_mockable_excludes_String() {
191        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(String.class);
192        assertThat(mockable.mockable()).isFalse();
193        assertThat(mockable.nonMockableReason()).contains("Cannot mock wrapper types, String.class or Class.class");
194    }
195
196    @Test
197    public void is_type_mockable_excludes_Class() {
198        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(Class.class);
199        assertThat(mockable.mockable()).isFalse();
200        assertThat(mockable.nonMockableReason()).contains("Cannot mock wrapper types, String.class or Class.class");
201    }
202
203    @Test
204    public void is_type_mockable_excludes_primitive_classes() {
205        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(int.class);
206        assertThat(mockable.mockable()).isFalse();
207        assertThat(mockable.nonMockableReason()).contains("primitive");
208    }
209
210    @Test
211    public void is_type_mockable_allows_anonymous() {
212        Observer anonymous = new Observer() {
213            @Override
214            public void update(Observable o, Object arg) {
215            }
216        };
217        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(anonymous.getClass());
218        assertThat(mockable.mockable()).isTrue();
219        assertThat(mockable.nonMockableReason()).contains("");
220    }
221
222    @Test
223    public void is_type_mockable_give_empty_reason_if_type_is_mockable() {
224        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(SomeClass.class);
225        assertThat(mockable.mockable()).isTrue();
226        assertThat(mockable.nonMockableReason()).isEqualTo("");
227    }
228
229    @Test
230    public void is_type_mockable_give_allow_final_mockable_from_JDK() {
231        MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(Pattern.class);
232        assertThat(mockable.mockable()).isTrue();
233        assertThat(mockable.nonMockableReason()).isEqualTo("");
234    }
235
236    @Test
237    public void test_parameters_retention() throws Exception {
238        assumeTrue(ClassFileVersion.ofThisVm().isAtLeast(JAVA_V8));
239        assumeTrue(ClassFileVersion.ofThisVm().isLessThan(JAVA_V9)); // Change when ByteBuddy has ASM6 - see #788
240
241        Class<?> typeWithParameters = new ByteBuddy()
242                .subclass(Object.class)
243                .defineMethod("foo", void.class, Visibility.PUBLIC)
244                .withParameter(String.class, "bar")
245                .intercept(StubMethod.INSTANCE)
246                .make()
247                .load(null)
248                .getLoaded();
249
250        MockCreationSettings<?> settings = settingsFor(typeWithParameters);
251        @SuppressWarnings("unchecked")
252        Object proxy = mockMaker.createMock(settings, new MockHandlerImpl(settings));
253
254        assertThat(proxy.getClass()).isEqualTo(typeWithParameters);
255        assertThat(new TypeDescription.ForLoadedType(typeWithParameters).getDeclaredMethods().filter(named("foo"))
256                .getOnly().getParameters().getOnly().getName()).isEqualTo("bar");
257    }
258
259    private static <T> MockCreationSettings<T> settingsFor(Class<T> type, Class<?>... extraInterfaces) {
260        MockSettingsImpl<T> mockSettings = new MockSettingsImpl<T>();
261        mockSettings.setTypeToMock(type);
262        mockSettings.defaultAnswer(new Returns("bar"));
263        if (extraInterfaces.length > 0) mockSettings.extraInterfaces(extraInterfaces);
264        return mockSettings;
265    }
266
267    @Test
268    public void testMockDispatcherIsRelocated() throws Exception {
269        assertThat(InlineByteBuddyMockMaker.class.getClassLoader().getResource("org/mockito/internal/creation/bytebuddy/MockMethodDispatcher.raw")).isNotNull();
270    }
271
272    private static final class FinalClass {
273
274        public String foo() {
275            return "foo";
276        }
277    }
278
279    private enum EnumClass {
280
281        INSTANCE;
282
283        public String foo() {
284            return "foo";
285        }
286    }
287
288    private abstract static class FinalMethodAbstractType {
289
290        public final String foo() {
291            return "foo";
292        }
293
294        public abstract String bar();
295    }
296
297    private static class FinalMethod {
298
299        public final String foo() {
300            return "foo";
301        }
302    }
303
304    private static class NonFinalMethod {
305
306        public String foo() {
307            return "foo";
308        }
309    }
310
311    private interface SampleInterface {
312
313        String bar();
314    }
315}
316