Reputation: 41
I've got web application which is split into two parts (being run in different jvms):
They communicate with each other via Spring Remoting:
(org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean
on @RestController layer and
org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
on @Service layer).
These two parts are deployed on different application servers. Mostly they are tested via Spring RestTemplate (@Service part has to be deployed and started manually and after that integration tests are run).
But as I used Spring Test and MockMvc in past and found it to be a great tool I would like to use again and again. Unfortunatelly I do not understand how can I add @Service layer context into test context configuration such way, that it would be accessible from test (which holds @RestController context augmented with some mocks).
If I manually start application server with @Service layer artifacts (on localhost) and run mine MockMvc-driven test I can see that remote requests from MockMvc get to their destination - @Service layer (through httpInvoker of course).
And I want to find possibility to start @Service layer context within test context (with all needed HttpInvokerServiceExporters). And to force httpInvoker to send its requests to this "pseudo" remote service (which in fact will be local).
Now I'm thinking about using embedded jetty for deploying @Service layer and running MockMvc tests against this instance. SpringHttpRemoting With EmbeddedJettyServer.wiki
I have very small experience in MicroService architecture but it seems that mine situation is rather usual for it. So maybe there are some more natural (in spite of Spring Test and MockMvc particularly) ways for such testing ?
Thanks in advance.
Andrey.
Upvotes: 1
Views: 1461
Reputation: 41
Well, let me share my thoughts upon the matter. Here is domain and api:
public class Contact implements Serializable {
private String firstName;
private String lastName;
private DateTime birthDate;
}
public interface ContactService {
List<String> getContacts();
}
@Service
public class ContactServiceImpl implements ContactService{
public List<String> getContacts() {
return new ArrayList<String>(asList("karl marx", " fridrih engels", " !!!"));
}
}
@RestController
public class ContactController {
public static final String QUALIFIER = "contactController";
public static final String MAPPING = "/contact";
@Autowired
private ContactService serviceInvoker;
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> findAll() {
List<String> strings = serviceInvoker.getContacts();
return new ResponseEntity<String>(Arrays.toString(strings.toArray()), HttpStatus.OK);
}
}
It is rather simple to test such configuration by providing test with 'invoking side' context (restcontroller in my case) and 'invoked side' context (containing real services, not remote proxies). Just as with monolith application context. Fast and easy approach. But in some cases not enough (e.g. You have customized HttpInvokerProxyFactoryBean on one side and customized HttpInvokerServiceExporter on the other for some purposes).
You can override HttpInvokerProxyFactoryBean class making it NonRemote.
First, modify HttpInvokerServiceExporter by overriding some of its methods; it is just needed to make methods connected to RemoteInvocation and RemoteInvocationResult public.
public class OpenedHttpServiceExporter extends HttpInvokerServiceExporter {
@Override
public RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException {
return super.readRemoteInvocation(request);
}
.
.
.
etc...
}
Let it be OpenedHttpServiceExporter. Create bean descriptor in test/resources, import production beans definitions which You need in test into it and add OpenedHttpServiceExporter bean with the same name as original HttpInvokerServiceExporter has - it is needed for overriding one with the other.
test context descriptor openedServiceExporter.xml (without beans element):
<import resource="classpath:spring/serviceExporter.xml"/>
<bean id="contactExporter" class="pmp.testingremoting.service.OpenedHttpServiceExporter">
<property name="service" ref="contactServiceImpl"/>
<property name="serviceInterface" value="pmp.testingremoting.service.ContactService"/>
</bean>
And imported descriptor:
<context:annotation-config/>
<context:component-scan base-package="pmp.testingremoting.service">
<context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
</context:component-scan>
<bean name="contactExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="contactServiceImpl"/>
<property name="serviceInterface" value="pmp.testingremoting.service.ContactService"/>
</bean>
Extend HttpInvokerProxyFactoryBean, make a bean of this subclass, autowire HttpInvokerServiceExporter field into it.
Override public Object invoke(MethodInvocation methodInvocation)
by calling OpenedHttpServiceExporter.invoke(createRemoteInvocation(methodInvocation), exporter.getService());
in it.
public class NonRemoteInvoker extends HttpInvokerProxyFactoryBean {
@Autowired
private OpenedHttpServiceExporter exporter;
public void setExporter(OpenedHttpServiceExporter exporter) {
this.exporter = exporter;
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return exporter.invoke(createRemoteInvocation(methodInvocation), exporter.getService());
}
}
Let's call this new class NonRemoteInvoker. It has to override only one method of super class and will serve as a bridge from 'invoking side' context to 'invoked side' context.
Create 'invoking side' test context descriptor (nonRemoteInvokerContext.xml) with an instance of NonRemoteInvoker (again, with same name as original HttpInvokerProxyFactoryBean has; also for overriding).
<import resource="classpath:spring/webContext.xml"/>
<bean id="serviceInvoker" class="pmp.testingremoting.controller.NonRemoteInvoker">
<property name="serviceUrl" value="http://localhost:8080/remote/ContactService" />
<property name="serviceInterface" value="pmp.testingremoting.service.ContactService" />
</bean>
and webContext.xml is
<mvc:annotation-driven/>
<context:annotation-config/>
<context:component-scan base-package="pmp.testingremoting.controller">
<context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
</context:component-scan>
Create test configuration for 'invoked side'. I used static @Configuration class and just imported test context descriptor into it.
@Configuration
@ImportResource(locations = {
"classpath:openedServiceExporter.xml"
})
static class TunedBusinessConfig {
}
Create test configuration for 'invoking side'. I did it the same way.
@Configuration
@ImportResource(locations = {
"classpath:nonRemoteInvokerContext.xml",
})
static class TunedRemoteInvokerConfig {
}
Now the test class. It will be marked via @WebAppConfiguration and have such @ContextHierarchy, that will allow 'invoking side' context to use 'invoked side' context (invoked - parent, invoking - child). It is needed for injecting OpenedHttpServiceExporter into NonRemoteInvoker.
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = {
ContactControllerIntegrationTest.TunedBusinessConfig.class
}),
@ContextConfiguration(classes = {
ContactControllerIntegrationTest.TunedRemoteInvokerConfig.class
})
})
public class ContactControllerIntegrationTest {
.
.
.
}
This approach allowed me to cover in test not only rest and service layers logic, but also logic of customized RemoteInvoker (let's call it transport logic).
Here are more details: https://github.com/PmPozitron/TestingRemoting/tree/lightVersion
I am not sure whether such approach is correct, so will not mark answer as accepted until appropriate moment.
Upvotes: 1