/*
* Copyright 2017 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.integration.test.context;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.Lifecycle;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.IntegrationConsumer;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.test.mock.MockMessageHandler;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* A {@link BeanFactoryAware} component with an API to customize real beans
* in the application context from test code.
* <p>
* The bean for this class is registered automatically via the {@link SpringIntegrationTest}
* annotation and can be autowired into test class.
*
* @author Artem Bilan
*
* @since 5.0
* @see SpringIntegrationTest
*/
public class MockIntegrationContext implements BeanFactoryAware {
public static final String MOCK_INTEGRATION_CONTEXT_BEAN_NAME = "mockIntegrationContext";
private final Map<String, Object> beans = new HashMap<>();
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(),
"a ConfigurableListableBeanFactory is required");
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
/**
* Reinstate the mocked beans after execution test to their real state.
* Typically is used from the {@link org.junit.After} method.
* @param beanNames the bean names to reset.
* If {@code null}, all the mocked beans are reset
*/
public void resetBeans(String... beanNames) {
final Collection<String> names;
if (!ObjectUtils.isEmpty(beanNames)) {
names = Arrays.asList(beanNames);
}
else {
names = null;
}
this.beans.entrySet()
.stream()
.filter(e -> names == null || names.contains(e.getKey()))
.forEach(e -> {
Object endpoint = this.beanFactory.getBean(e.getKey());
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(endpoint);
if (endpoint instanceof SourcePollingChannelAdapter) {
directFieldAccessor.setPropertyValue("source", e.getValue());
}
else if (endpoint instanceof IntegrationConsumer) {
directFieldAccessor.setPropertyValue("handler", e.getValue());
}
});
}
/**
* Replace the real {@link MessageSource} in the {@link SourcePollingChannelAdapter} bean
* with provided {@link MessageSource} instance.
* Can be a mock object.
* @param pollingAdapterId the endpoint bean name
* @param mockMessageSource the {@link MessageSource} to replace in the endpoint bean
* @see org.springframework.integration.test.mock.MockIntegration#mockMessageSource
*/
public void instead(String pollingAdapterId, MessageSource<?> mockMessageSource) {
instead(pollingAdapterId, mockMessageSource, true);
}
/**
* Replace the real {@link MessageSource} in the {@link SourcePollingChannelAdapter} bean
* with provided {@link MessageSource} instance.
* Can be a mock object.
* The endpoint is not started when {@code autoStartup == false}.
* @param pollingAdapterId the endpoint bean name
* @param mockMessageSource the {@link MessageSource} to replace in the endpoint bean
* @param autoStartup start or not the endpoint after replacing its {@link MessageSource}
* @see org.springframework.integration.test.mock.MockIntegration#mockMessageSource
*/
public void instead(String pollingAdapterId, MessageSource<?> mockMessageSource, boolean autoStartup) {
instead(pollingAdapterId, mockMessageSource, SourcePollingChannelAdapter.class, "source", autoStartup);
}
public void instead(String consumerEndpointId, MessageHandler mockMessageHandler) {
instead(consumerEndpointId, mockMessageHandler, true);
}
public void instead(String consumerEndpointId, MessageHandler mockMessageHandler, boolean autoStartup) {
Object endpoint = this.beanFactory.getBean(consumerEndpointId, IntegrationConsumer.class);
if (autoStartup && endpoint instanceof Lifecycle) {
((Lifecycle) endpoint).stop();
}
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(endpoint);
Object targetMessageHandler = directFieldAccessor.getPropertyValue("handler");
this.beans.put(consumerEndpointId, targetMessageHandler);
if (mockMessageHandler instanceof MessageProducer) {
if (targetMessageHandler instanceof MessageProducer) {
MessageChannel outputChannel = TestUtils.getPropertyValue(targetMessageHandler, "outputChannel",
MessageChannel.class);
((MessageProducer) mockMessageHandler).setOutputChannel(outputChannel);
}
else {
if (mockMessageHandler instanceof MockMessageHandler) {
if (TestUtils.getPropertyValue(mockMessageHandler, "hasReplies", Boolean.class)) {
throw new IllegalStateException("The [" + mockMessageHandler + "] " +
"with replies can't replace simple MessageHandler [" + targetMessageHandler + "]");
}
}
else {
throw new IllegalStateException("The MessageProducer handler [" + mockMessageHandler + "] " +
"can't replace simple MessageHandler [" + targetMessageHandler + "]");
}
}
}
directFieldAccessor.setPropertyValue("handler", mockMessageHandler);
if (autoStartup && endpoint instanceof Lifecycle) {
((Lifecycle) endpoint).start();
}
}
private void instead(String endpointId, Object messagingComponent, Class<?> endpointClass, String property,
boolean autoStartup) {
Object endpoint = this.beanFactory.getBean(endpointId, endpointClass);
if (autoStartup && endpoint instanceof Lifecycle) {
((Lifecycle) endpoint).stop();
}
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(endpoint);
this.beans.put(endpointId, directFieldAccessor.getPropertyValue(property));
directFieldAccessor.setPropertyValue(property, messagingComponent);
if (autoStartup && endpoint instanceof Lifecycle) {
((Lifecycle) endpoint).start();
}
}
}