I met a problem in test of my application,I dont understand what i need to do if i want to replace ImapIdleChannelAdapter as it is
written in the spring documentation
public class ImapConfiguration{
#Bean
ImapMailReceiver getReceiver() {
ImapMailReceiver receiver = new ImapMailReceiver(ImapConfig.getUri());
return receiver;
}
#Bean
ImapIdleChannelAdapter getAdapter(ImapMailReceiver receiver) {
ImapIdleChannelAdapter adapter = new InternalImapIdleChannelAdapter(receiver);
adapter.setAutoStartup(true);
return adapter;
}
#Bean
StandardIntegrationFlow getFlow(ImapIdleChannelAdapter adapter, GenericHandler handler) {
return IntegrationFlows.from(adapter)
.handle(handler)
.get();
}
}
In the spring integration documentation in the MockIntegration section says that "The MockIntegration factory provides an API to build mocks for Spring Integration beans that are parts of the integration flow (MessageSource, MessageProducer, MessageHandler, and MessageChannel).You can use the target mocks during the configuration phase as well as in the target test method to replace the real endpoints before performing verifications and assertions". I haven't found any examples using MessageProducer in the spring integration documentation and the Spring Integration Samples repository on github. I wrote test to try replace ImapIdleChannelAdapter
#SpringBootTest(classes = ImapConfiguration.class)
#Import({ReceiverTestConf.class})
#SpringIntegrationTest(noAutoStartup = "inboundChannelAdapter")
public class ImapMailReceiverTest {
#Captor
ArgumentCaptor<ReceivedMail> emailCaptor = ArgumentCaptor.forClass(ReceivedMail.class);
#MockBean
TestEmailHandler emailHandlerTestImpl;
#Autowired
TestImapReceiver imapReceiver;
#Autowired
MockIntegrationContext mockIntegrationContext;
#Test
#SneakyThrows
void receive() throws MessagingException {
Mockito.doNothing().when(emailHandlerTestImpl).handle(Mockito.any());
MessageSource<MimeMessage> message = () -> {
return new GenericMessage<>("testMessage");
};
this.mockIntegrationContext.substituteMessageSourceFor("imapIdleChannelAdapter", MockIntegration.mockMessageSource(message));
idleChannelAdapter.start();
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
Mockito.verify(emailHandlerTestImpl, Mockito.times(1)).handle(emailCaptor.capture());
List<ReceivedMail> result = emailCaptor.getAllValues();
Assertions.assertEquals(1, result.size());
}
);
}
When I run the test, I am getting the exception.
Bean named 'imapIdleChannelAdapter' is expected to be of type 'org.springframework.integration.endpoint.SourcePollingChannelAdapter' but was actually of type 'com.test.emailadapter.imap.InternalImapIdleChannelAdapter'
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'imapIdleChannelAdapter' is expected to be of type 'org.springframework.integration.endpoint.SourcePollingChannelAdapter' but was actually of type 'com.test.emailadapter.imap.InternalImapIdleChannelAdapter'
at app//org.springframework.beans.factory.support.AbstractBeanFactory.adaptBeanInstance(AbstractBeanFactory.java:417)
at app//org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:398)
at app//org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
at app//org.springframework.integration.test.context.MockIntegrationContext.substituteMessageSourceFor(MockIntegrationContext.java:217)
at app//org.springframework.integration.test.context.MockIntegrationContext.substituteMessageSourceFor(MockIntegrationContext.java:157)
at app//org.springframework.integration.test.context.MockIntegrationContext.substituteMessageSourceFor(MockIntegrationContext.java:142)
I believe the sentence in the doc needs some improvements. I definitely remember that there were some ambitions to be able mock everything in the flow. Therefore we mention over there a MessageProducer and MessageChannel as well. However in practice it turns out that we don't need to mock message channels since they can be supplied with ChannelInterceptor to verify various interaction with the channel in the flow.
The MessageProducer is also pointless to mock since you simply can emit a test message into the channel this producer is going to produce in the production. So, what you need so far is just stop this MessageProducer before the test and deal with its channel in the test already.
I see you already do a proper noAutoStartup = "inboundChannelAdapter" for your test class.
Since you don't have channel declared in your flow, the channel is auto-created by the framework with the pattern for name: [IntegrationFlow.beanName].channel#[channelNameIndex]. So, the output channel for your IntegrationFlows.from(adapter) is a DirectChannel with a getFlow.channel#0 bean name.
Please, consider to raise a GH issue, so we will improve the doc for that MockIntegration.
Related
I'm trying to save a step-related state, that would be accessible from processor. For this purpose I made a class and a bean for it. My configuration file looks like this:
#Slf4j
#Configuration
#EnableScheduling
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class MyConfiguration
{
// Job, reader and writer beans
#Bean("myStep")
Step myStep(#Qualifier("myReader") ItemReader<InputEntity> reader,
#Qualifier("myProcessor") ItemProcessor<InputEntity, OutputEntity> processor,
#Qualifier("myWriter") ItemWriter<OutputEntity> writer)
{
return stepBuilderFactory
.get("myStep")
.<InputEntity, OutputEntity> chunk(100)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
#StepScope
#Bean("myProcessor")
public MyProcessor processingStep(StateService s)
{
var processor = new MyProcessor();
processor.setStateService(s);
return processor;
}
#Scope(value = "step", proxyMode = ScopedProxyMode.NO)
#Bean
public StateService stateService()
{
return new StateService();
}
}
Idea behind is to create a state service for each new step execution (the class is empty at the moment and doesn't have #Component annotation). However, I get in trouble with Spring proxies:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'MyProcessor' is expected to be of type 'very.long.package.name.steps.MyProcessor' but was actually of type 'com.sun.proxy.$Proxy265'
Gathering already answered questions and dozens of guides I tried following:
All possible proxy modes of stateService bean;
Injecting this bean directly into MyProcessor via #Autowired variable
Annotating configuration with #EnableBatchProcessing
Calling stateService() bean directly: processor.setStateService(stateService());
Injecting bean into step Step bean. In this case I have to change the method signature, so the method accepts MyProcessor instead of ItemProcessor<InputEntity, OutputEntity> to expose the variable
Nothing helped, I still get this exception. What am I misunderstanding in concept of #StepScope? How can I store some state for particular step execution?
I read this, this and even this, but neither helped me to understand it.
What am I misunderstanding in concept of #StepScope?
The step scope is designed for beans that should be lazily instantiated at runtime when the step is executed. By default, for interface based beans, Spring will create JDK dynamic proxies, and for classes it will use CGLib.
In the code you shared, you used proxyMode = ScopedProxyMode.NO, so you have explicitly told Spring to not create a proxy for that bean, hence your error. And that's why when you change it to proxyMode = ScopedProxyMode.INTERFACES it works.
How can I store some state for particular step execution?
The execution context is designed for this very use case (among other use cases). So you can store that state in the ExecutionContext of the step and retrieve afterwards in the same job or when restarting a failed job.
Well, adding an interface helps in this case:
#Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
#Bean
public IStateService stateService()
{
return new StateService();
}
I also had to add new interface IStateService, which I actually don't need. However, it still doesn't answer the question WHY, and doesn't bring any understanding either.
I have created a custom channel in Spring cloud stream with custom inputs and outputs. Let's suppose this is the created channel:
public interface Channel {
String FOO = "foo-request";
String BAR = "bar-response";
#Input(FOO)
SubscribableChannel fooRequest();
#Output(BAR)
MessageChannel barResponse();
}
Something.java:
public class Something{
#Autowired
private Channel channel;
public void doSomething(..){
// Do some steps
channel.barRequest().send(MessageBuilder.withPayload(outputMessage).build())
}
}
As it can be seen I am injecting the custom channel in the Something class to send the message at the end of a method.
When I would like to test this method, I am having some issues with the injection done in the Something class. I cannot inject the Something class because it's not a component. But this class injects a Channel object as it can be seen. So here is what I have done to pass the limitation of injecting an internal property for this class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyChannel.class})
public class SomethingTest{
#Autowired
private Channel myChannel;
#Test
public void TestDoSomething(){
// cannot inject it as it does not have any qualified bean
Something something = new Something();
ReflectionTestUtils.setField(something, "channel", channel);
}
#EnableBinding(Channel.class)
public static class MyChannel {
}
}
Without the ReflectionTestUtls line, I am getting a NullPointerException on channel.barRequest().send() in the doSomething method. With having this line to pass the injected object, I am getting the following error:
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.bar-response'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
First of all, I am not sure if what I am doing is the best way of dealing with my custom channel and testing the corresponding method, so please let me know if there is a better way. Second, why am I getting this exception and how I can address it?
P.S: I have already set the required configurations in my application.yml file for the test related to the binders and channels in a similar way that I have been doing with running the application in a normal way. This approach has been working so far with other properties.
the first thing noticing is you try to send to your Input Channel which typically recevies the messages.
Try changing it to channel.barResponse().send(...)
Other then that i am facing the same issue, but with a more complicated use-case. We have already definied our rabbit queues.
There are articles on how to test Spring cloud stream applications without connecting to a messaging system with spring-cloud-stream-test-support. But I want to really connect to RabbitMQ from my integration test, and cannot do that. Here is test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#EnableBinding(Source.class)
public class StreamIT {
#Autowired
private Source source;
#Test
public void testMessageSending() throws InterruptedException {
source.output().send(MessageBuilder.withPayload("test").build());
System.out.println("Message sent.");
}
}
Everything is the same as in #SpringBootApplication, they use the same properties from application.yml.
But there is no log line that message is sent (o.s.a.r.c.CachingConnectionFactory : Created new connection: SpringAMQP#22e79d25:0/SimpleConnection#5cce3ab6 [delegate=amqp://guest#127.0.1.1:5672/, localPort= 60934]
),
and even if broker is not started, there is no java.net.ConnectException: Connection refused (Connection refused).
Am I doing something wrong? What is needed to create real connection to broker and send message from test?
Since you are using #SpringBootTest annotation in your test, Spring Boot will evaluate all available auto-configurations.
If you have spring-cloud-stream-test-support dependency in your test classpath then following auto-configurations will be also evaluated:
org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration
org.springframework.cloud.stream.test.binder.MessageCollectorAutoConfiguration
As a result, you have only one binder in the application context - org.springframework.cloud.stream.test.binder.TestSupportBinder. By its name, you can understand that it does nothing about real binding.
Excluding/removing of spring-cloud-stream-test-support dependency from test classpath - is a dubious solution. Since it forces you to create two separate modules for unit and integration tests.
If you want to exclude previously mentioned auto-configurations in your test. You can do it as follows:
#RunWith(SpringRunner.class)
#SpringBootTest
#EnableAutoConfiguration(exclude = {TestSupportBinderAutoConfiguration.class, MessageCollectorAutoConfiguration.class})
public class StreamIT {
EDIT
You need to remove the test-support jar from the pom. It's presence (in test scope) is what triggers replacing the real binder with a test binder.
After removing the test binder support, this works fine for me...
#RunWith(SpringRunner.class)
#SpringBootTest
public class So49816044ApplicationTests {
#Autowired
private Source source;
#Autowired
private AmqpAdmin admin;
#Autowired
private RabbitTemplate template;
#Test
public void test() {
// bind an autodelete queue to the destination exchange
Queue queue = this.admin.declareQueue();
this.admin.declareBinding(new Binding(queue.getName(), DestinationType.QUEUE, "mydest", "#", null));
this.source.output().send(new GenericMessage<>("foo"));
this.template.setReceiveTimeout(10_000);
Message received = template.receive(queue.getName());
assertThat(received.getBody()).isEqualTo("foo".getBytes());
}
}
Although there is not a rabbit sample; there is a kafka sample that uses a real (embedded) kafka binder for testing, although the test jar is excluded, it doesn't explicitly say that's needed.
I've been looking at the Spring integration ip module, I wanted to create UDP channel for receiving, but I found I can only do it with XML.
I was thinking that I could make something out if I looked inside the implementation code, but it creates bean definition itself, from parameters supplied in xml.
I can't use xml definitions in my code, is there a way to make it work with spring without xml?
alternatively, is there any better way in java to work with udp?
Starting with version 5.0 there is Java DSL on the matter already, so the code for UDP Channel Adapters may look like:
#Bean
public IntegrationFlow inUdpAdapter() {
return IntegrationFlows.from(Udp.inboundAdapter(0))
.channel(udpIn())
.get();
}
#Bean
public QueueChannel udpIn() {
return new QueueChannel();
}
#Bean
public IntegrationFlow outUdpAdapter() {
return f -> f.handle(Udp.outboundAdapter(m -> m.getHeaders().get("udp_dest")));
}
But with existing Spring Integration version you can simply configure UnicastReceivingChannelAdapter bean:
#Bean
public UnicastReceivingChannelAdapter udpInboundAdapter() {
UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(1111);
unicastReceivingChannelAdapter.setOutputChannel(udpChannel());
return unicastReceivingChannelAdapter;
}
In the Reference Manual you can find the Tips and Tricks chapter for some info how to write Spring Integration application with raw Java and annotation configuration.
I added JIRA to address Java sample in the Reference Manual.
Assume I have made a simple client in my application that uses a remote web service that is exposing a RESTful API at some URI /foo/bar/{baz}. Now I wish to unit test my client that makes calls to this web service.
Ideally, in my tests, I’d like to mock the responses I get from the web service, given a specific request like /foo/bar/123 or /foo/bar/42. My client assumes the API is actually running somewhere, so I need a local "web service" to start running on http://localhost:9090/foo/bar for my tests.
I want my unit tests to be self-contained, similar to testing Spring controllers with the Spring MVC Test framework.
Some pseudo-code for a simple client, fetching numbers from the remote API:
// Initialization logic involving setting up mocking of remote API at
// http://localhost:9090/foo/bar
#Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar
#Test
public void getNumber42() {
onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
assertEquals(42, numberClient.getNumber(42));
}
// ..
What are my alternatives using Spring?
Best method is to use WireMock.
Add the following dependencies:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.0.6</version>
</dependency>
Define and use the wiremock as shown below
#Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json").withBody(response)));
If you use Spring RestTemplate you can use MockRestServiceServer. An example can be found here REST Client Testing With MockRestServiceServer.
If you want to unit test your client, then you'd mock out the services that are making the REST API calls, i.e. with mockito - I assume you do have a service that is making those API calls for you, right?
If on the other hand you want to "mock out" the rest APIs in that there is some sort of server giving you responses, which would be more in line of integration testing, you could try one of the many framework out there like restito, rest-driver or betamax.
You can easily use Mockito to mock a REST API in Spring Boot.
Put a stubbed controller in your test tree:
#RestController
public class OtherApiHooks {
#PostMapping("/v1/something/{myUUID}")
public ResponseEntity<Void> handlePost(#PathVariable("myUUID") UUID myUUID ) {
assert (false); // this function is meant to be mocked, not called
return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
}
}
Your client will need to call the API on localhost when running tests. This could be configured in src/test/resources/application.properties. If the test is using RANDOM_PORT, your client under test will need to find that value. This is a bit tricky, but the issue is addressed here: Spring Boot - How to get the running port
Configure your test class to use a WebEnvironment (a running server) and now your test can use Mockito in the standard way, returning ResponseEntity objects as needed:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {
#MockBean private OtherApiHooks otherApiHooks;
#Test public void test1() {
Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
.when(otherApiHooks).handlePost(any());
clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
Mockito.verify(otherApiHooks).handlePost(eq(id));
}
}
You can also use this for end-to-end testing of your entire microservice in an environment with the mock created above. One way to do this is to inject TestRestTemplate into your test class, and use that to call your REST API in place of clientFunctionUnderTest from the example.
#Autowired private TestRestTemplate restTemplate;
#LocalServerPort private int localPort; // you're gonna need this too
How this works
Because OtherApiHooks is a #RestController in the test tree, Spring Boot will automatically establish the specified REST service when running the SpringBootTest.WebEnvironment.
Mockito is used here to mock the controller class -- not the service as a whole. Therefore, there will be some server-side processing managed by Spring Boot before the mock is hit. This may include such things as deserializing (and validating) the path UUID shown in the example.
From what I can tell, this approach is robust for parallel test runs with IntelliJ and Maven.
What you are looking for is the support for Client-side REST Tests in the Spring MVC Test Framework.
Assuming your NumberClient uses Spring's RestTemplate, this aforementioned support is the way to go!
Hope this helps,
Sam
Here is a basic example on how to mock a Controller class with Mockito:
The Controller class:
#RestController
#RequestMapping("/users")
public class UsersController {
#Autowired
private UserService userService;
public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
Page<UserProfile> page = userService.getAllUsers(pageable);
List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
}
}
Configure the beans:
#Configuration
public class UserConfig {
#Bean
public UsersController usersController() {
return new UsersController();
}
#Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
The UserCollectionItemDto is a simple POJO and it represents what the API consumer sends to the server. The UserProfile is the main object used in the service layer (by the UserService class). This behaviour also implements the DTO pattern.
Finally, mockup the expected behaviour:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#Import(UserConfig.class)
public class UsersControllerTest {
#Autowired
private UsersController usersController;
#Autowired
private UserService userService;
#Test
public void getAllUsers() {
initGetAllUsersRules();
PageRequest pageable = new PageRequest(0, 10);
Page<UserDto> page = usersController.getUsers(pageable);
assertTrue(page.getNumberOfElements() == 1);
}
private void initGetAllUsersRules() {
Page<UserProfile> page = initPage();
when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
}
private Page<UserProfile> initPage() {
PageRequest pageRequest = new PageRequest(0, 10);
PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
return page;
}
private List<UserProfile> getUsersList() {
UserProfile userProfile = new UserProfile();
List<UserProfile> userProfiles = new ArrayList<>();
userProfiles.add(userProfile);
return userProfiles;
}
}
The idea is to use the pure Controller bean and mockup its members. In this example, we mocked the UserService.getUsers() object to contain a user and then validated whether the Controller would return the right number of users.
With the same logic you can test the Service and other levels of your application. This example uses the Controller-Service-Repository Pattern as well :)