Home | History | Annotate | Download | only in constructor
      1 /*
      2  * Copyright (c) 2017 Mockito contributors
      3  * This program is made available under the terms of the MIT License.
      4  */
      5 package org.mockitousage.constructor;
      6 
      7 import java.util.List;
      8 import org.junit.Test;
      9 import org.mockito.exceptions.base.MockitoException;
     10 import org.mockito.mock.SerializableMode;
     11 import org.mockitousage.IMethods;
     12 import org.mockitoutil.TestBase;
     13 
     14 import static org.junit.Assert.assertEquals;
     15 import static org.junit.Assert.assertNull;
     16 import static org.junit.Assert.fail;
     17 import static org.assertj.core.api.Assertions.assertThat;
     18 import static org.mockito.Mockito.CALLS_REAL_METHODS;
     19 import static org.mockito.Mockito.mock;
     20 import static org.mockito.Mockito.spy;
     21 import static org.mockito.Mockito.when;
     22 import static org.mockito.Mockito.withSettings;
     23 
     24 public class CreatingMocksWithConstructorTest extends TestBase {
     25 
     26     static abstract class AbstractMessage {
     27         private final String message;
     28         AbstractMessage() {
     29             this.message = "hey!";
     30         }
     31         AbstractMessage(String message) {
     32             this.message = message;
     33         }
     34         AbstractMessage(int i) {
     35             this.message = String.valueOf(i);
     36         }
     37         String getMessage() {
     38             return message;
     39         }
     40     }
     41 
     42     static class Message extends AbstractMessage {}
     43     class InnerClass extends AbstractMessage {}
     44 
     45     @Test
     46     public void can_create_mock_with_constructor() {
     47         Message mock = mock(Message.class, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS));
     48         //the message is a part of state of the mocked type that gets initialized in constructor
     49         assertEquals("hey!", mock.getMessage());
     50     }
     51 
     52     @Test
     53     public void can_mock_abstract_classes() {
     54         AbstractMessage mock = mock(AbstractMessage.class, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS));
     55         assertEquals("hey!", mock.getMessage());
     56     }
     57 
     58     @Test
     59     public void can_spy_abstract_classes() {
     60         AbstractMessage mock = spy(AbstractMessage.class);
     61         assertEquals("hey!", mock.getMessage());
     62     }
     63 
     64     @Test
     65     public void can_spy_abstract_classes_with_constructor_args() {
     66         AbstractMessage mock = mock(AbstractMessage.class, withSettings().useConstructor("hello!").defaultAnswer(CALLS_REAL_METHODS));
     67         assertEquals("hello!", mock.getMessage());
     68     }
     69 
     70     @Test
     71     public void can_spy_abstract_classes_with_constructor_primitive_args() {
     72         AbstractMessage mock = mock(AbstractMessage.class, withSettings().useConstructor(7).defaultAnswer(CALLS_REAL_METHODS));
     73         assertEquals("7", mock.getMessage());
     74     }
     75 
     76     @Test
     77     public void can_spy_abstract_classes_with_constructor_array_of_nulls() {
     78         AbstractMessage mock = mock(AbstractMessage.class, withSettings().useConstructor(new Object[]{null}).defaultAnswer(CALLS_REAL_METHODS));
     79         assertNull(mock.getMessage());
     80     }
     81 
     82     @Test
     83     public void can_spy_abstract_classes_with_casted_null() {
     84         AbstractMessage mock = mock(AbstractMessage.class, withSettings().useConstructor((String) null).defaultAnswer(CALLS_REAL_METHODS));
     85         assertNull(mock.getMessage());
     86     }
     87 
     88     @Test
     89     public void can_spy_abstract_classes_with_null_varargs() {
     90         try {
     91             mock(AbstractMessage.class, withSettings().useConstructor(null).defaultAnswer(CALLS_REAL_METHODS));
     92             fail();
     93         } catch (IllegalArgumentException e) {
     94             assertThat(e).hasMessageContaining("constructorArgs should not be null. " +
     95                 "If you need to pass null, please cast it to the right type, e.g.: useConstructor((String) null)");
     96         }
     97     }
     98 
     99     @Test
    100     public void can_mock_inner_classes() {
    101         InnerClass mock = mock(InnerClass.class, withSettings().useConstructor().outerInstance(this).defaultAnswer(CALLS_REAL_METHODS));
    102         assertEquals("hey!", mock.getMessage());
    103     }
    104 
    105     public static class ThrowingConstructorClass{
    106         public ThrowingConstructorClass() {
    107             throw new RuntimeException();
    108         }
    109     }
    110 
    111     @Test
    112     public void explains_constructor_exceptions() {
    113         try {
    114             mock(ThrowingConstructorClass.class, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS));
    115             fail();
    116         } catch (MockitoException e) {
    117             assertThat(e).hasRootCauseInstanceOf(RuntimeException.class);
    118             assertThat(e.getCause()).hasMessageContaining("Please ensure the target class has a 0-arg constructor and executes cleanly.");
    119         }
    120     }
    121 
    122     static class HasConstructor {
    123         HasConstructor(String x) {}
    124     }
    125 
    126     @Test
    127     public void exception_message_when_constructor_not_found() {
    128         try {
    129             //when
    130             spy(HasConstructor.class);
    131             //then
    132             fail();
    133         } catch (MockitoException e) {
    134             assertThat(e).hasMessage("Unable to create mock instance of type 'HasConstructor'");
    135             assertThat(e.getCause()).hasMessageContaining("Please ensure that the target class has a 0-arg constructor.");
    136         }
    137     }
    138 
    139     static class Base {}
    140     static class ExtendsBase extends Base {}
    141     static class ExtendsExtendsBase extends ExtendsBase {}
    142 
    143     static class UsesBase {
    144         public UsesBase(Base b) {
    145             constructorUsed = "Base";
    146         }
    147         public UsesBase(ExtendsBase b) {
    148             constructorUsed = "ExtendsBase";
    149         }
    150 
    151         private String constructorUsed = null;
    152         String getConstructorUsed() {
    153             return constructorUsed;
    154         }
    155     }
    156 
    157     @Test
    158     public void can_mock_unambigous_constructor_with_inheritance_base_class_exact_match() {
    159         UsesBase u = mock(UsesBase.class, withSettings().useConstructor(new Base()).defaultAnswer(CALLS_REAL_METHODS));
    160         assertEquals("Base", u.getConstructorUsed());
    161     }
    162 
    163     @Test
    164     public void can_mock_unambigous_constructor_with_inheritance_extending_class_exact_match() {
    165         UsesBase u = mock(UsesBase.class, withSettings().useConstructor(new ExtendsBase()).defaultAnswer(CALLS_REAL_METHODS));
    166         assertEquals("ExtendsBase", u.getConstructorUsed());
    167     }
    168 
    169     @Test
    170     public void can_mock_unambigous_constructor_with_inheritance_non_exact_match() {
    171         UsesBase u = mock(UsesBase.class, withSettings().useConstructor(new ExtendsExtendsBase()).defaultAnswer(CALLS_REAL_METHODS));
    172         assertEquals("ExtendsBase", u.getConstructorUsed());
    173     }
    174 
    175     static class UsesTwoBases {
    176         public UsesTwoBases(Base b1, Base b2) {
    177             constructorUsed = "Base,Base";
    178         }
    179         public UsesTwoBases(ExtendsBase b1, Base b2) {
    180             constructorUsed = "ExtendsBase,Base";
    181         }
    182         public UsesTwoBases(Base b1, ExtendsBase b2) {
    183             constructorUsed = "Base,ExtendsBase";
    184         }
    185 
    186         private String constructorUsed = null;
    187         String getConstructorUsed() {
    188             return constructorUsed;
    189         }
    190     }
    191 
    192     @Test
    193     public void can_mock_unambigous_constructor_with_inheritance_multiple_base_class_exact_match() {
    194         UsesTwoBases u =
    195             mock(UsesTwoBases.class, withSettings().useConstructor(new Base(), new Base()).defaultAnswer(CALLS_REAL_METHODS));
    196         assertEquals("Base,Base", u.getConstructorUsed());
    197     }
    198 
    199     @Test
    200     public void can_mock_unambigous_constructor_with_inheritance_first_extending_class_exact_match() {
    201         UsesTwoBases u =
    202             mock(UsesTwoBases.class, withSettings().useConstructor(new ExtendsBase(), new Base()).defaultAnswer(CALLS_REAL_METHODS));
    203         assertEquals("ExtendsBase,Base", u.getConstructorUsed());
    204     }
    205 
    206     @Test
    207     public void can_mock_unambigous_constructor_with_inheritance_second_extending_class_exact_match() {
    208         UsesTwoBases u =
    209             mock(UsesTwoBases.class, withSettings().useConstructor(new Base(), new ExtendsBase()).defaultAnswer(CALLS_REAL_METHODS));
    210         assertEquals("Base,ExtendsBase", u.getConstructorUsed());
    211     }
    212 
    213     @Test
    214     public void fail_when_multiple_matching_constructors_with_inheritence() {
    215         try {
    216             //when
    217             mock(UsesTwoBases.class, withSettings().useConstructor(new ExtendsBase(), new ExtendsBase()));
    218             //then
    219             fail();
    220         } catch (MockitoException e) {
    221             //TODO the exception message includes Mockito internals like the name of the generated class name.
    222             //I suspect that we could make this exception message nicer.
    223             assertThat(e).hasMessage("Unable to create mock instance of type 'UsesTwoBases'");
    224             assertThat(e.getCause())
    225                 .hasMessageContaining("Multiple constructors could be matched to arguments of types "
    226                     + "[org.mockitousage.constructor.CreatingMocksWithConstructorTest$ExtendsBase, "
    227                     + "org.mockitousage.constructor.CreatingMocksWithConstructorTest$ExtendsBase]")
    228                 .hasMessageContaining("If you believe that Mockito could do a better job deciding on which constructor to use, please let us know.\n" +
    229                     "Ticket 685 contains the discussion and a workaround for ambiguous constructors using inner class.\n" +
    230                     "See https://github.com/mockito/mockito/issues/685");
    231         }
    232     }
    233 
    234     @Test
    235     public void mocking_inner_classes_with_wrong_outer_instance() {
    236         try {
    237             //when
    238             mock(InnerClass.class, withSettings().useConstructor().outerInstance(123).defaultAnswer(CALLS_REAL_METHODS));
    239             //then
    240             fail();
    241         } catch (MockitoException e) {
    242             assertThat(e).hasMessage("Unable to create mock instance of type 'InnerClass'");
    243             //TODO it would be nice if all useful information was in the top level exception, instead of in the exception's cause
    244             //also applies to other scenarios in this test
    245             assertThat(e.getCause()).hasMessageContaining(
    246                 "Please ensure that the target class has a 0-arg constructor"
    247                     + " and provided outer instance is correct.");
    248         }
    249     }
    250 
    251     @Test
    252     public void mocking_interfaces_with_constructor() {
    253         //at the moment this is allowed however we can be more strict if needed
    254         //there is not much sense in creating a spy of an interface
    255         mock(IMethods.class, withSettings().useConstructor());
    256         spy(IMethods.class);
    257     }
    258 
    259     @Test
    260     public void prevents_across_jvm_serialization_with_constructor() {
    261         try {
    262             //when
    263             mock(AbstractMessage.class, withSettings().useConstructor().serializable(SerializableMode.ACROSS_CLASSLOADERS));
    264             //then
    265             fail();
    266         } catch (MockitoException e) {
    267             assertEquals("Mocks instantiated with constructor cannot be combined with " + SerializableMode.ACROSS_CLASSLOADERS + " serialization mode.", e.getMessage());
    268         }
    269     }
    270 
    271     static abstract class AbstractThing {
    272         abstract String name();
    273         String fullName() {
    274             return "abstract " + name();
    275         }
    276     }
    277 
    278     @Test
    279     public void abstract_method_returns_default() {
    280         AbstractThing thing = spy(AbstractThing.class);
    281         assertEquals("abstract null", thing.fullName());
    282     }
    283 
    284     @Test
    285     public void abstract_method_stubbed() {
    286         AbstractThing thing = spy(AbstractThing.class);
    287         when(thing.name()).thenReturn("me");
    288         assertEquals("abstract me", thing.fullName());
    289     }
    290 
    291     @Test
    292     public void interface_method_stubbed() {
    293         List<?> list = spy(List.class);
    294         when(list.size()).thenReturn(12);
    295         assertEquals(12, list.size());
    296     }
    297 
    298     @Test
    299     public void calls_real_interface_method() {
    300         List list = mock(List.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
    301         assertNull(list.get(1));
    302     }
    303 
    304     @Test
    305     public void handles_bridge_methods_correctly() {
    306         SomeConcreteClass<Integer> testBug = spy(new SomeConcreteClass<Integer>());
    307         assertEquals("value", testBug.getValue(0));
    308     }
    309 
    310     public abstract class SomeAbstractClass<T> {
    311 
    312         protected abstract String getRealValue(T value);
    313 
    314         public String getValue(T value) {
    315             return getRealValue(value);
    316         }
    317     }
    318 
    319     public class SomeConcreteClass<T extends Number> extends SomeAbstractClass<T> {
    320 
    321         @Override
    322         protected String getRealValue(T value) {
    323             return "value";
    324         }
    325     }
    326 
    327     private static class AmbiguousWithPrimitive {
    328         public AmbiguousWithPrimitive(String s, int i) {
    329             data = s;
    330         }
    331         public AmbiguousWithPrimitive(Object o, int i) {
    332             data = "just an object";
    333         }
    334 
    335         private String data;
    336         public String getData() {
    337             return data;
    338         }
    339     }
    340 
    341     @Test
    342     public void can_spy_ambiguius_constructor_with_primitive() {
    343         AmbiguousWithPrimitive mock = mock(AmbiguousWithPrimitive.class, withSettings().useConstructor("String", 7).defaultAnswer(CALLS_REAL_METHODS));
    344         assertEquals("String", mock.getData());
    345     }
    346 }
    347