1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockitousage.annotation;
6
7import java.util.AbstractList;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.LinkedList;
11import java.util.List;
12import org.junit.Rule;
13import org.junit.Test;
14import org.junit.rules.ExpectedException;
15import org.mockito.Mock;
16import org.mockito.MockitoAnnotations;
17import org.mockito.Spy;
18import org.mockito.exceptions.base.MockitoException;
19import org.mockitoutil.TestBase;
20
21import static junit.framework.TestCase.assertEquals;
22import static junit.framework.TestCase.assertNotNull;
23import static junit.framework.TestCase.assertTrue;
24import static junit.framework.TestCase.fail;
25import static org.assertj.core.api.Assertions.assertThat;
26import static org.mockito.Mockito.doReturn;
27import static org.mockito.Mockito.never;
28import static org.mockito.Mockito.verify;
29import static org.mockito.Mockito.when;
30
31@SuppressWarnings("unused")
32public class SpyAnnotationTest extends TestBase {
33
34    @Spy
35    final List<String> spiedList = new ArrayList<String>();
36
37    @Spy
38    InnerStaticClassWithNoArgConstructor staticTypeWithNoArgConstructor;
39
40    @Spy
41    InnerStaticClassWithoutDefinedConstructor staticTypeWithoutDefinedConstructor;
42
43    @Rule
44    public final ExpectedException shouldThrow = ExpectedException.none();
45
46    @Test
47    public void should_init_spy_by_instance() throws Exception {
48        doReturn("foo").when(spiedList).get(10);
49        assertEquals("foo", spiedList.get(10));
50        assertTrue(spiedList.isEmpty());
51    }
52
53    @Test
54    public void should_init_spy_and_automatically_create_instance() throws Exception {
55        when(staticTypeWithNoArgConstructor.toString()).thenReturn("x");
56        when(staticTypeWithoutDefinedConstructor.toString()).thenReturn("y");
57        assertEquals("x", staticTypeWithNoArgConstructor.toString());
58        assertEquals("y", staticTypeWithoutDefinedConstructor.toString());
59    }
60
61    @Test
62    public void should_allow_spying_on_interfaces() throws Exception {
63        class WithSpy {
64            @Spy
65            List<String> list;
66        }
67
68        WithSpy withSpy = new WithSpy();
69        MockitoAnnotations.initMocks(withSpy);
70        when(withSpy.list.size()).thenReturn(3);
71        assertEquals(3, withSpy.list.size());
72    }
73
74    @Test
75    public void should_allow_spying_on_interfaces_when_instance_is_concrete() throws Exception {
76        class WithSpy {
77            @Spy
78            List<String> list = new LinkedList<String>();
79        }
80        WithSpy withSpy = new WithSpy();
81
82        //when
83        MockitoAnnotations.initMocks(withSpy);
84
85        //then
86        verify(withSpy.list, never()).clear();
87    }
88
89    @Test
90    public void should_report_when_no_arg_less_constructor() throws Exception {
91        class FailingSpy {
92            @Spy
93            NoValidConstructor noValidConstructor;
94        }
95
96        try {
97            MockitoAnnotations.initMocks(new FailingSpy());
98            fail();
99        } catch (MockitoException e) {
100            assertThat(e.getMessage()).contains("Please ensure that the type")
101                    .contains(NoValidConstructor.class.getSimpleName())
102                    .contains("has a no-arg constructor");
103        }
104    }
105
106    @Test
107    public void should_report_when_constructor_is_explosive() throws Exception {
108        class FailingSpy {
109            @Spy
110            ThrowingConstructor throwingConstructor;
111        }
112
113        try {
114            MockitoAnnotations.initMocks(new FailingSpy());
115            fail();
116        } catch (MockitoException e) {
117            assertThat(e.getMessage()).contains("Unable to create mock instance");
118        }
119    }
120
121    @Test
122    public void should_spy_abstract_class() throws Exception {
123        class SpyAbstractClass {
124            @Spy
125            AbstractList<String> list;
126
127            List<String> asSingletonList(String s) {
128                when(list.size()).thenReturn(1);
129                when(list.get(0)).thenReturn(s);
130                return list;
131            }
132        }
133        SpyAbstractClass withSpy = new SpyAbstractClass();
134        MockitoAnnotations.initMocks(withSpy);
135        assertEquals(Arrays.asList("a"), withSpy.asSingletonList("a"));
136    }
137
138    @Test
139    public void should_spy_inner_class() throws Exception {
140
141        class WithMockAndSpy {
142            @Spy
143            private InnerStrength strength;
144            @Mock
145            private List<String> list;
146
147            abstract class InnerStrength {
148                private final String name;
149
150                InnerStrength() {
151                    // Make sure that @Mock fields are always injected before @Spy fields.
152                    assertNotNull(list);
153                    // Make sure constructor is indeed called.
154                    this.name = "inner";
155                }
156
157                abstract String strength();
158
159                String fullStrength() {
160                    return name + " " + strength();
161                }
162            }
163        }
164        WithMockAndSpy outer = new WithMockAndSpy();
165        MockitoAnnotations.initMocks(outer);
166        when(outer.strength.strength()).thenReturn("strength");
167        assertEquals("inner strength", outer.strength.fullStrength());
168    }
169
170    @Test(expected = IndexOutOfBoundsException.class)
171    public void should_reset_spy() throws Exception {
172        spiedList.get(10); // see shouldInitSpy
173    }
174
175    @Test
176    public void should_report_when_enclosing_instance_is_needed() throws Exception {
177        class Outer {
178            class Inner {
179            }
180        }
181        class WithSpy {
182            @Spy
183            private Outer.Inner inner;
184        }
185        try {
186            MockitoAnnotations.initMocks(new WithSpy());
187            fail();
188        } catch (MockitoException e) {
189            assertThat(e).hasMessageContaining("@Spy annotation can only initialize inner classes");
190        }
191    }
192
193    @Test
194    public void should_report_private_inner_not_supported() throws Exception {
195        try {
196            MockitoAnnotations.initMocks(new WithInnerPrivate());
197            fail();
198        } catch (MockitoException e) {
199            // Currently fails at instantiation time, because the mock subclass don't have the
200            // 1-arg constructor expected for the outerclass.
201            // org.mockito.internal.creation.instance.ConstructorInstantiator.withParams()
202            assertThat(e).hasMessageContaining("Unable to initialize @Spy annotated field 'spy_field'")
203                    .hasMessageContaining(WithInnerPrivate.InnerPrivate.class.getSimpleName());
204        }
205    }
206
207    @Test
208    public void should_report_private_abstract_inner_not_supported() throws Exception {
209        try {
210            MockitoAnnotations.initMocks(new WithInnerPrivateAbstract());
211            fail();
212        } catch (MockitoException e) {
213            assertThat(e).hasMessageContaining("@Spy annotation can't initialize private abstract inner classes")
214                    .hasMessageContaining(WithInnerPrivateAbstract.class.getSimpleName())
215                    .hasMessageContaining(WithInnerPrivateAbstract.InnerPrivateAbstract.class.getSimpleName())
216                    .hasMessageContaining("You should augment the visibility of this inner class");
217        }
218    }
219
220    @Test
221    public void should_report_private_static_abstract_inner_not_supported() throws Exception {
222        try {
223            MockitoAnnotations.initMocks(new WithInnerPrivateStaticAbstract());
224            fail();
225        } catch (MockitoException e) {
226            assertThat(e).hasMessageContaining("@Spy annotation can't initialize private abstract inner classes")
227                    .hasMessageContaining(WithInnerPrivateStaticAbstract.class.getSimpleName())
228                    .hasMessageContaining(WithInnerPrivateStaticAbstract.InnerPrivateStaticAbstract.class.getSimpleName())
229                    .hasMessageContaining("You should augment the visibility of this inner class");
230        }
231    }
232
233    static class WithInnerPrivateStaticAbstract {
234        @Spy
235        private InnerPrivateStaticAbstract spy_field;
236
237        private static abstract class InnerPrivateStaticAbstract {
238        }
239    }
240    static class WithInnerPrivateAbstract {
241        @Spy
242        private InnerPrivateAbstract spy_field;
243
244        public void some_method() {
245            new InnerPrivateConcrete();
246        }
247
248        private abstract class InnerPrivateAbstract {
249        }
250
251        private class InnerPrivateConcrete extends InnerPrivateAbstract {
252
253        }
254    }
255
256    static class WithInnerPrivate {
257        @Spy
258        private InnerPrivate spy_field;
259
260        private class InnerPrivate {
261        }
262
263        private class InnerPrivateSub extends InnerPrivate {}
264    }
265
266    static class InnerStaticClassWithoutDefinedConstructor {
267    }
268
269    static class InnerStaticClassWithNoArgConstructor {
270        InnerStaticClassWithNoArgConstructor() {
271        }
272
273        InnerStaticClassWithNoArgConstructor(String f) {
274        }
275    }
276
277    static class NoValidConstructor {
278        NoValidConstructor(String f) {
279        }
280    }
281
282    static class ThrowingConstructor {
283        ThrowingConstructor() {
284            throw new RuntimeException("boo!");
285        }
286    }
287}
288