package org.mockito.internal.creation.bytebuddy; import net.bytebuddy.ByteBuddy; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.implementation.StubMethod; import org.junit.Test; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.creation.MockSettingsImpl; import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.internal.handler.MockHandlerImpl; import org.mockito.internal.stubbing.answers.Returns; import org.mockito.internal.util.collections.Sets; import org.mockito.mock.MockCreationSettings; import org.mockito.mock.SerializableMode; import org.mockito.plugins.MockMaker; import java.util.HashMap; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.regex.Pattern; import static net.bytebuddy.ClassFileVersion.JAVA_V8; import static net.bytebuddy.ClassFileVersion.JAVA_V9; import static net.bytebuddy.matcher.ElementMatchers.named; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.junit.Assume.assumeTrue; public class InlineByteBuddyMockMakerTest extends AbstractByteBuddyMockMakerTest<InlineByteBuddyMockMaker> { public InlineByteBuddyMockMakerTest() { super(new InlineByteBuddyMockMaker()); } @Override protected Class<?> mockTypeOf(Class<?> type) { return type; } @Test public void should_create_mock_from_final_class() throws Exception { MockCreationSettings<FinalClass> settings = settingsFor(FinalClass.class); FinalClass proxy = mockMaker.createMock(settings, new MockHandlerImpl<FinalClass>(settings)); assertThat(proxy.foo()).isEqualTo("bar"); } @Test public void should_create_mock_from_final_class_in_the_JDK() throws Exception { assumeTrue(ClassFileVersion.ofThisVm().isLessThan(JAVA_V9)); // Change when ByteBuddy has ASM6 - see #788 MockCreationSettings<Pattern> settings = settingsFor(Pattern.class); Pattern proxy = mockMaker.createMock(settings, new MockHandlerImpl<Pattern>(settings)); assertThat(proxy.pattern()).isEqualTo("bar"); } @Test public void should_create_mock_from_abstract_class_with_final_method() throws Exception { MockCreationSettings<FinalMethodAbstractType> settings = settingsFor(FinalMethodAbstractType.class); FinalMethodAbstractType proxy = mockMaker.createMock(settings, new MockHandlerImpl<FinalMethodAbstractType>(settings)); assertThat(proxy.foo()).isEqualTo("bar"); assertThat(proxy.bar()).isEqualTo("bar"); } @Test public void should_create_mock_from_final_class_with_interface_methods() throws Exception { MockCreationSettings<FinalMethod> settings = settingsFor(FinalMethod.class, SampleInterface.class); FinalMethod proxy = mockMaker.createMock(settings, new MockHandlerImpl<FinalMethod>(settings)); assertThat(proxy.foo()).isEqualTo("bar"); assertThat(((SampleInterface) proxy).bar()).isEqualTo("bar"); } @Test public void should_create_mock_from_hashmap() throws Exception { assumeTrue(ClassFileVersion.ofThisVm().isLessThan(JAVA_V9)); // Change when ByteBuddy has ASM6 - see #862 MockCreationSettings<HashMap> settings = settingsFor(HashMap.class); HashMap proxy = mockMaker.createMock(settings, new MockHandlerImpl<HashMap>(settings)); assertThat(proxy.get(null)).isEqualTo("bar"); } @Test @SuppressWarnings("unchecked") public void should_throw_exception_redefining_unmodifiable_class() { MockCreationSettings settings = settingsFor(int.class); try { mockMaker.createMock(settings, new MockHandlerImpl(settings)); fail("Expected a MockitoException"); } catch (MockitoException e) { e.printStackTrace(); assertThat(e).hasMessageContaining("Could not modify all classes"); } } @Test @SuppressWarnings("unchecked") public void should_throw_exception_redefining_array() { int[] array = new int[5]; MockCreationSettings<? extends int[]> settings = settingsFor(array.getClass()); try { mockMaker.createMock(settings, new MockHandlerImpl(settings)); fail("Expected a MockitoException"); } catch (MockitoException e) { assertThat(e).hasMessageContaining("Arrays cannot be mocked"); } } @Test public void should_create_mock_from_enum() throws Exception { MockCreationSettings<EnumClass> settings = settingsFor(EnumClass.class); EnumClass proxy = mockMaker.createMock(settings, new MockHandlerImpl<EnumClass>(settings)); assertThat(proxy.foo()).isEqualTo("bar"); } @Test public void should_fail_at_creating_a_mock_of_a_final_class_with_explicit_serialization() throws Exception { MockCreationSettings<FinalClass> settings = new CreationSettings<FinalClass>() .setTypeToMock(FinalClass.class) .setSerializableMode(SerializableMode.BASIC); try { mockMaker.createMock(settings, new MockHandlerImpl<FinalClass>(settings)); fail("Expected a MockitoException"); } catch (MockitoException e) { assertThat(e) .hasMessageContaining("Unsupported settings") .hasMessageContaining("serialization") .hasMessageContaining("extra interfaces"); } } @Test public void should_fail_at_creating_a_mock_of_a_final_class_with_extra_interfaces() throws Exception { MockCreationSettings<FinalClass> settings = new CreationSettings<FinalClass>() .setTypeToMock(FinalClass.class) .setExtraInterfaces(Sets.<Class<?>>newSet(List.class)); try { mockMaker.createMock(settings, new MockHandlerImpl<FinalClass>(settings)); fail("Expected a MockitoException"); } catch (MockitoException e) { assertThat(e) .hasMessageContaining("Unsupported settings") .hasMessageContaining("serialization") .hasMessageContaining("extra interfaces"); } } @Test public void should_remove_recursive_self_call_from_stack_trace() throws Exception { StackTraceElement[] stack = new StackTraceElement[]{ new StackTraceElement("foo", "", "", -1), new StackTraceElement(SampleInterface.class.getName(), "", "", -1), new StackTraceElement("qux", "", "", -1), new StackTraceElement("bar", "", "", -1), new StackTraceElement("baz", "", "", -1) }; Throwable throwable = new Throwable(); throwable.setStackTrace(stack); throwable = InlineByteBuddyMockMaker.hideRecursiveCall(throwable, 2, SampleInterface.class); assertThat(throwable.getStackTrace()).isEqualTo(new StackTraceElement[]{ new StackTraceElement("foo", "", "", -1), new StackTraceElement("bar", "", "", -1), new StackTraceElement("baz", "", "", -1) }); } @Test public void should_handle_missing_or_inconsistent_stack_trace() throws Exception { Throwable throwable = new Throwable(); throwable.setStackTrace(new StackTraceElement[0]); assertThat(InlineByteBuddyMockMaker.hideRecursiveCall(throwable, 0, SampleInterface.class)).isSameAs(throwable); } @Test public void should_provide_reason_for_wrapper_class() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(Integer.class); assertThat(mockable.nonMockableReason()).isEqualTo("Cannot mock wrapper types, String.class or Class.class"); } @Test public void should_provide_reason_for_vm_unsupported() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(int[].class); assertThat(mockable.nonMockableReason()).isEqualTo("VM does not not support modification of given type"); } @Test public void is_type_mockable_excludes_String() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(String.class); assertThat(mockable.mockable()).isFalse(); assertThat(mockable.nonMockableReason()).contains("Cannot mock wrapper types, String.class or Class.class"); } @Test public void is_type_mockable_excludes_Class() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(Class.class); assertThat(mockable.mockable()).isFalse(); assertThat(mockable.nonMockableReason()).contains("Cannot mock wrapper types, String.class or Class.class"); } @Test public void is_type_mockable_excludes_primitive_classes() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(int.class); assertThat(mockable.mockable()).isFalse(); assertThat(mockable.nonMockableReason()).contains("primitive"); } @Test public void is_type_mockable_allows_anonymous() { Observer anonymous = new Observer() { @Override public void update(Observable o, Object arg) { } }; MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(anonymous.getClass()); assertThat(mockable.mockable()).isTrue(); assertThat(mockable.nonMockableReason()).contains(""); } @Test public void is_type_mockable_give_empty_reason_if_type_is_mockable() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(SomeClass.class); assertThat(mockable.mockable()).isTrue(); assertThat(mockable.nonMockableReason()).isEqualTo(""); } @Test public void is_type_mockable_give_allow_final_mockable_from_JDK() { MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(Pattern.class); assertThat(mockable.mockable()).isTrue(); assertThat(mockable.nonMockableReason()).isEqualTo(""); } @Test public void test_parameters_retention() throws Exception { assumeTrue(ClassFileVersion.ofThisVm().isAtLeast(JAVA_V8)); assumeTrue(ClassFileVersion.ofThisVm().isLessThan(JAVA_V9)); // Change when ByteBuddy has ASM6 - see #788 Class<?> typeWithParameters = new ByteBuddy() .subclass(Object.class) .defineMethod("foo", void.class, Visibility.PUBLIC) .withParameter(String.class, "bar") .intercept(StubMethod.INSTANCE) .make() .load(null) .getLoaded(); MockCreationSettings<?> settings = settingsFor(typeWithParameters); @SuppressWarnings("unchecked") Object proxy = mockMaker.createMock(settings, new MockHandlerImpl(settings)); assertThat(proxy.getClass()).isEqualTo(typeWithParameters); assertThat(new TypeDescription.ForLoadedType(typeWithParameters).getDeclaredMethods().filter(named("foo")) .getOnly().getParameters().getOnly().getName()).isEqualTo("bar"); } private static <T> MockCreationSettings<T> settingsFor(Class<T> type, Class<?>... extraInterfaces) { MockSettingsImpl<T> mockSettings = new MockSettingsImpl<T>(); mockSettings.setTypeToMock(type); mockSettings.defaultAnswer(new Returns("bar")); if (extraInterfaces.length > 0) mockSettings.extraInterfaces(extraInterfaces); return mockSettings; } @Test public void testMockDispatcherIsRelocated() throws Exception { assertThat(InlineByteBuddyMockMaker.class.getClassLoader().getResource("org/mockito/internal/creation/bytebuddy/MockMethodDispatcher.raw")).isNotNull(); } private static final class FinalClass { public String foo() { return "foo"; } } private enum EnumClass { INSTANCE; public String foo() { return "foo"; } } private abstract static class FinalMethodAbstractType { public final String foo() { return "foo"; } public abstract String bar(); } private static class FinalMethod { public final String foo() { return "foo"; } } private static class NonFinalMethod { public String foo() { return "foo"; } } private interface SampleInterface { String bar(); } }