diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java index 0521c27f4b9..50d8e23a02f 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,10 +24,10 @@ import java.util.Set; import org.mockito.Captor; import org.mockito.MockitoAnnotations; -import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; @@ -37,16 +37,35 @@ import org.springframework.util.ReflectionUtils.FieldCallback; * annotations. * * @author Phillip Webb + * @author Andy Wilkinson * @since 1.4.2 */ public class MockitoTestExecutionListener extends AbstractTestExecutionListener { @Override public void prepareTestInstance(TestContext testContext) throws Exception { + initMocks(testContext); + injectFields(testContext); + } + + @Override + public void beforeTestMethod(TestContext testContext) throws Exception { + if (Boolean.TRUE.equals(testContext.getAttribute( + DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) { + initMocks(testContext); + reinjectFields(testContext); + } + } + + @Override + public int getOrder() { + return 1950; + } + + private void initMocks(TestContext testContext) { if (hasMockitoAnnotations(testContext)) { MockitoAnnotations.initMocks(testContext.getTestInstance()); } - injectFields(testContext); } private boolean hasMockitoAnnotations(TestContext testContext) { @@ -56,21 +75,46 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener } private void injectFields(TestContext testContext) { + postProcessFields(testContext, new MockitoFieldHandler() { + + @Override + public void handle(MockitoField mockitoField, + MockitoPostProcessor postProcessor) { + postProcessor.inject(mockitoField.field, mockitoField.target, + mockitoField.definition); + } + + }); + } + + private void reinjectFields(final TestContext testContext) { + postProcessFields(testContext, new MockitoFieldHandler() { + + @Override + public void handle(MockitoField mockitoField, + MockitoPostProcessor postProcessor) { + ReflectionUtils.makeAccessible(mockitoField.field); + ReflectionUtils.setField(mockitoField.field, + testContext.getTestInstance(), null); + postProcessor.inject(mockitoField.field, mockitoField.target, + mockitoField.definition); + } + + }); + } + + private void postProcessFields(TestContext testContext, MockitoFieldHandler handler) { DefinitionsParser parser = new DefinitionsParser(); parser.parse(testContext.getTestClass()); if (!parser.getDefinitions().isEmpty()) { - injectFields(testContext, parser); - } - } - - private void injectFields(TestContext testContext, DefinitionsParser parser) { - ApplicationContext applicationContext = testContext.getApplicationContext(); - MockitoPostProcessor postProcessor = applicationContext - .getBean(MockitoPostProcessor.class); - for (Definition definition : parser.getDefinitions()) { - Field field = parser.getField(definition); - if (field != null) { - postProcessor.inject(field, testContext.getTestInstance(), definition); + MockitoPostProcessor postProcessor = testContext.getApplicationContext() + .getBean(MockitoPostProcessor.class); + for (Definition definition : parser.getDefinitions()) { + Field field = parser.getField(definition); + if (field != null) { + handler.handle(new MockitoField(field, testContext.getTestInstance(), + definition), postProcessor); + } } } } @@ -98,4 +142,26 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener } + private static final class MockitoField { + + private final Field field; + + private final Object target; + + private final Definition definition; + + private MockitoField(Field field, Object instance, Definition definition) { + this.field = field; + this.target = instance; + this.definition = definition; + } + + } + + private interface MockitoFieldHandler { + + void handle(MockitoField mockitoField, MockitoPostProcessor postProcessor); + + } + } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java new file mode 100644 index 00000000000..e766d0f514a --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.mock.mockito; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.example.ExampleService; +import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.annotation.DirtiesContext.MethodMode; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Integration tests for using {@link MockBean} with {@link DirtiesContext} and + * {@link MethodMode#BEFORE_METHOD}. + * + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) +public class MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests { + + @MockBean + private ExampleService exampleService; + + @Autowired + private ExampleServiceCaller caller; + + @Test + public void testMocking() throws Exception { + given(this.exampleService.greeting()).willReturn("Boot"); + assertThat(this.caller.sayGreeting()).isEqualTo("I say Boot"); + } + + @Configuration + @Import(ExampleServiceCaller.class) + static class Config { + + } + +} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java index d3c5ce1a66d..3ce40000f67 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.mockito.MockitoAnnotations; import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -35,6 +36,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests for {@link MockitoTestExecutionListener}. @@ -78,6 +80,28 @@ public class MockitoTestExecutionListenerTests { assertThat(this.fieldCaptor.getValue().getName()).isEqualTo("mockBean"); } + @Test + public void beforeTestMethodShouldDoNothingWhenDirtiesContextAttributeIsNotSet() + throws Exception { + WithMockBean instance = new WithMockBean(); + this.listener.beforeTestMethod(mockTestContext(instance)); + verifyNoMoreInteractions(this.postProcessor); + } + + @Test + public void beforeTestMethodShouldInjectMockBeanWhenDirtiesContextAttributeIsSet() + throws Exception { + WithMockBean instance = new WithMockBean(); + TestContext mockTestContext = mockTestContext(instance); + given(mockTestContext.getAttribute( + DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE)) + .willReturn(Boolean.TRUE); + this.listener.beforeTestMethod(mockTestContext); + verify(this.postProcessor).inject(this.fieldCaptor.capture(), eq(instance), + (MockDefinition) any()); + assertThat(this.fieldCaptor.getValue().getName()).isEqualTo("mockBean"); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private TestContext mockTestContext(Object instance) { TestContext testContext = mock(TestContext.class); diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java new file mode 100644 index 00000000000..21d67c8da87 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.mock.mockito; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; +import org.springframework.boot.test.mock.mockito.example.SimpleExampleService; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.annotation.DirtiesContext.MethodMode; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.Mockito.verify; + +/** + * Integration tests for using {@link SpyBean} with {@link DirtiesContext} and + * {@link MethodMode#BEFORE_METHOD}. + * + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) +public class SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests { + + @SpyBean + private SimpleExampleService exampleService; + + @Autowired + private ExampleServiceCaller caller; + + @Test + public void testSpying() throws Exception { + this.caller.sayGreeting(); + verify(this.exampleService).greeting(); + } + + @Configuration + @Import(ExampleServiceCaller.class) + static class Config { + + } + +}