33import io .dinject .core .BeanContextFactory ;
44import io .dinject .core .Builder ;
55import io .dinject .core .BuilderFactory ;
6+ import io .dinject .core .EnrichBean ;
67import io .dinject .core .SuppliedBean ;
78import org .slf4j .Logger ;
89import org .slf4j .LoggerFactory ;
1617import java .util .Map ;
1718import java .util .ServiceLoader ;
1819import java .util .Set ;
20+ import java .util .function .Consumer ;
1921
2022/**
2123 * Boot and create a bean context with options for shutdown hook and supplying test doubles.
@@ -54,6 +56,8 @@ public class BootContext {
5456
5557 private final List <SuppliedBean > suppliedBeans = new ArrayList <>();
5658
59+ private final List <EnrichBean > enrichBeans = new ArrayList <>();
60+
5761 private final Set <String > includeModules = new LinkedHashSet <>();
5862
5963 private boolean ignoreMissingModuleDependencies ;
@@ -149,38 +153,41 @@ public BootContext withIgnoreMissingModuleDependencies() {
149153 }
150154
151155 /**
152- * Supply a bean to the context that will be used instead of any similar bean in the context.
156+ * Supply a bean to the context that will be used instead of any
157+ * similar bean in the context.
153158 * <p>
154- * This is typically expected to be used in tests and the bean supplied is typically a test double
155- * or mock.
159+ * This is typically expected to be used in tests and the bean
160+ * supplied is typically a test double or mock.
156161 * </p>
157162 *
158163 * <pre>{@code
159164 *
160- * @Test
161- * public void someComponentTest() {
165+ * Pump pump = mock(Pump.class);
166+ * Grinder grinder = mock(Grinder.class);
162167 *
163- * MyRedisApi mockRedis = mock(MyRedisApi.class);
164- * MyDbApi mockDatabase = mock(MyDbApi.class);
168+ * try (BeanContext context = new BootContext()
169+ * .withBeans(pump, grinder)
170+ * .load()) {
165171 *
166- * try (BeanContext context = new BootContext()
167- * .withBeans(mockRedis, mockDatabase)
168- * .load()) {
172+ * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
173+ * coffeeMaker.makeIt();
169174 *
170- * // built with test doubles injected ...
171- * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
172- * coffeeMaker.makeIt();
175+ * Pump pump1 = context.getBean(Pump.class);
176+ * Grinder grinder1 = context.getBean(Grinder.class);
173177 *
174- * assertThat(...
175- * }
176- * }
178+ * assertThat(pump1).isSameAs(pump);
179+ * assertThat(grinder1).isSameAs(grinder);
177180 *
181+ * verify(pump).pumpWater();
182+ * verify(grinder).grindBeans();
183+ * }
178184 *
179185 * }</pre>
180186 *
181187 * @param beans The bean used when injecting a dependency for this bean or the interface(s) it implements
182188 * @return This BootContext
183189 */
190+ @ SuppressWarnings ("unchecked" )
184191 public BootContext withBeans (Object ... beans ) {
185192 for (Object bean : beans ) {
186193 suppliedBeans .add (new SuppliedBean (suppliedType (bean .getClass ()), bean ));
@@ -194,11 +201,148 @@ public BootContext withBeans(Object... beans) {
194201 * This is typically a test double often created by Mockito or similar.
195202 * </p>
196203 *
204+ * <pre>{@code
205+ *
206+ * try (BeanContext context = new BootContext()
207+ * .withBean(Pump.class, mock)
208+ * .load()) {
209+ *
210+ * Pump pump = context.getBean(Pump.class);
211+ * assertThat(pump).isSameAs(mock);
212+ *
213+ * // act
214+ * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
215+ * coffeeMaker.makeIt();
216+ *
217+ * verify(pump).pumpSteam();
218+ * verify(pump).pumpWater();
219+ * }
220+ *
221+ * }</pre>
222+ *
197223 * @param type The dependency injection type this bean is target for
198224 * @param bean The supplied bean instance to use (typically a test mock)
199225 */
200- public BootContext withBean (Class <?> type , Object bean ) {
201- suppliedBeans .add (new SuppliedBean (type , bean ));
226+ public <D > BootContext withBean (Class <D > type , D bean ) {
227+ suppliedBeans .add (new SuppliedBean <>(type , bean ));
228+ return this ;
229+ }
230+
231+ /**
232+ * Use a mockito mock when injecting this bean type.
233+ *
234+ * <pre>{@code
235+ *
236+ * try (BeanContext context = new BootContext()
237+ * .withMock(Pump.class)
238+ * .withMock(Grinder.class, grinder -> {
239+ * // setup the mock
240+ * when(grinder.grindBeans()).thenReturn("stub response");
241+ * })
242+ * .load()) {
243+ *
244+ *
245+ * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
246+ * coffeeMaker.makeIt();
247+ *
248+ * // this is a mockito mock
249+ * Grinder grinder = context.getBean(Grinder.class);
250+ * verify(grinder).grindBeans();
251+ * }
252+ *
253+ * }</pre>
254+ */
255+ public <D > BootContext withMock (Class <D > type ) {
256+ return withMock (type , null );
257+ }
258+
259+ /**
260+ * Use a mockito mock when injecting this bean type additionally
261+ * running setup on the mock instance.
262+ *
263+ * <pre>{@code
264+ *
265+ * try (BeanContext context = new BootContext()
266+ * .withMock(Pump.class)
267+ * .withMock(Grinder.class, grinder -> {
268+ *
269+ * // setup the mock
270+ * when(grinder.grindBeans()).thenReturn("stub response");
271+ * })
272+ * .load()) {
273+ *
274+ *
275+ * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
276+ * coffeeMaker.makeIt();
277+ *
278+ * // this is a mockito mock
279+ * Grinder grinder = context.getBean(Grinder.class);
280+ * verify(grinder).grindBeans();
281+ * }
282+ *
283+ * }</pre>
284+ */
285+ public <D > BootContext withMock (Class <D > type , Consumer <D > consumer ) {
286+ suppliedBeans .add (new SuppliedBean <>(type , null , consumer ));
287+ return this ;
288+ }
289+
290+ /**
291+ * Use a mockito spy when injecting this bean type.
292+ *
293+ * <pre>{@code
294+ *
295+ * try (BeanContext context = new BootContext()
296+ * .withSpy(Pump.class)
297+ * .load()) {
298+ *
299+ * // setup spy here ...
300+ * Pump pump = context.getBean(Pump.class);
301+ * doNothing().when(pump).pumpSteam();
302+ *
303+ * // act
304+ * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
305+ * coffeeMaker.makeIt();
306+ *
307+ * verify(pump).pumpWater();
308+ * verify(pump).pumpSteam();
309+ * }
310+ *
311+ * }</pre>
312+ */
313+ public <D > BootContext withSpy (Class <D > type ) {
314+ return withSpy (type , null );
315+ }
316+
317+ /**
318+ * Use a mockito spy when injecting this bean type additionally
319+ * running setup on the spy instance.
320+ *
321+ * <pre>{@code
322+ *
323+ * try (BeanContext context = new BootContext()
324+ * .withSpy(Pump.class, pump -> {
325+ * // setup the spy
326+ * doNothing().when(pump).pumpWater();
327+ * })
328+ * .load()) {
329+ *
330+ * // or setup here ...
331+ * Pump pump = context.getBean(Pump.class);
332+ * doNothing().when(pump).pumpSteam();
333+ *
334+ * // act
335+ * CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
336+ * coffeeMaker.makeIt();
337+ *
338+ * verify(pump).pumpWater();
339+ * verify(pump).pumpSteam();
340+ * }
341+ *
342+ * }</pre>
343+ */
344+ public <D > BootContext withSpy (Class <D > type , Consumer <D > consumer ) {
345+ enrichBeans .add (new EnrichBean <>(type , consumer ));
202346 return this ;
203347 }
204348
@@ -214,7 +358,7 @@ public BeanContext load() {
214358 Set <String > moduleNames = factoryOrder .orderFactories ();
215359 log .debug ("building context with modules {}" , moduleNames );
216360
217- Builder rootBuilder = BuilderFactory .newRootBuilder (suppliedBeans );
361+ Builder rootBuilder = BuilderFactory .newRootBuilder (suppliedBeans , enrichBeans );
218362
219363 for (BeanContextFactory factory : factoryOrder .factories ()) {
220364 rootBuilder .addChild (factory .createContext (rootBuilder ));
0 commit comments