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.