Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide option in MockRestServiceServer where an instance of MockMvc is used to obtain the response [SPR-9917] #14550

Closed
spring-projects-issues opened this issue Oct 24, 2012 · 9 comments
Assignees
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

Rossen Stoyanchev opened SPR-9917 and commented

Currently MockRestServiceServer allows defining expected requests and the stub response that should be returned for each expected request.

In some cases, where tests involve both client- and server-side code, it would be useful if instead of defining stub responses, the responses were obtained by invoking the server-side Spring MVC Test support (i.e. an instance of MockMvc) to obtain a MockHttpServletResponse and adapt it to a client-side response. This would enable tests that span both client and server side code.

This is based on question raised by Jeff Holmes in presentation at SpringOne.


Affects: 3.2 M2

1 votes, 3 watchers

@spring-projects-issues
Copy link
Collaborator Author

Jeff Holmes commented

Just to provide a little background motivation, I had implemented an integration test using RestTemplate on client side to invoke MVC controller, and used an embedded servlet (winstone, or jetty would do) to run MVC controller. With this feature, it would be possible to avoid use of a servlet container, separate threads, network communication, and still test post conditions after client side and server side code executes (in my case I set up an embedded db that's updated by both client and server).

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

I've added a MockMvc-based ClientHttpRequestFactory that can set on RestTemplate instance so that requests are executed by calling the MockMvc instance. There is an example test as well. It would be good to hear if it works out. Marking as resolved for now but it can be re-opened.

@spring-projects-issues
Copy link
Collaborator Author

Jeff Holmes commented

Rossen, very nice. I was able to get this to work in my scenario, which was using xml config and profiles (took a bit of extra code to get profiles to be recognized within Controller context). Very satisfying to get rid of embedded servlet container :)
Much appreciated,
Jeff

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration()
@ActiveProfiles(profiles = {"dev", "property-datasource"})
public class SuccessfulStepTest {

	@Autowired
	private RestStepExecutionService restStepExecutionService;
        ...

	@Before
	public void setup() {
		
	    String[] contextFiles = {"com/vmware/vfra/batch/web/servlet-context.xml"};
	    XmlWebApplicationContext context = new XmlWebApplicationContext();
	    context.getEnvironment().setActiveProfiles("dev", "property-datasource");
	    context.setConfigLocations(contextFiles);
	    context.setServletContext(new MockServletContext());
	    context.refresh();

		MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context)
				.alwaysExpect(status().isAccepted()).build();

		RestTemplate restTemplate = new RestTemplate(
				new MockMvcClientHttpRequestFactory(mockMvc));
		// used by MasterPartitionHandler
		restStepExecutionService.setRestTemplate(restTemplate);
		restStepExecutionService.setRestUrl("/job/{jobExecutionId}/step/{stepId}/name/{stepName}");
	}
	
	@Test
	public void testSuccessfulStep() throws Exception {
                // job PartitionHandler uses restStepExecutionService, now wired with restTemplate above, to
                // send REST message to MVC Controller not shown. Both client and server side make updates to H2 DB,
                // initialized by test. This version, with MockMvc in place of embedded servlet container (Winstone or
                // Jetty), avoids separate thread, container initialization, network layer.
		JobExecution exec = jobLauncherTestUtils
				.launchJob(new JobParametersBuilder().addLong("jobTimeStamp",
						Calendar.getInstance().getTimeInMillis())
						.toJobParameters());
		Assert.assertEquals(BatchStatus.COMPLETED, exec.getStatus());
		Assert.assertEquals(10, reportingJdbcTemplate
				.queryForInt("select count(*) from reporting.orders"));
		Assert.assertEquals(
				1,
				reportingJdbcTemplate
						.queryForInt("select order_type from reporting.orders where order_id = 1"));
               ...

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Jeff Holmes, thanks for confirming this. Can you elaborate on the issue with the profiles? In other words I would expect this to work:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("com/vmware/vfra/batch/web/servlet-context.xml")
@ActiveProfiles(profiles = {"dev", "property-datasource"})
public class SuccessfulStepTest {

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private RestStepExecutionService restStepExecutionService;
        ...

    @Before
    public void setup() {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
                .alwaysExpect(status().isAccepted()).build();

        RestTemplate restTemplate = new RestTemplate(
                new MockMvcClientHttpRequestFactory(mockMvc));
        // used by MasterPartitionHandler
        restStepExecutionService.setRestTemplate(restTemplate);
        restStepExecutionService.setRestUrl("/job/{jobExecutionId}/step/{stepId}/name/{stepName}");
  }        

  ...

}

The reason it is important is because when the TestContext framework loads the Spring configuration, it caches. Otherwise it's reloaded before every test.

/cc Sam Brannen

@spring-projects-issues
Copy link
Collaborator Author

Jeff Holmes commented

If I removed the line that set the profile on the Environment, the web application context wouldn't recognize that active profiles were set and would fail to load a number of beans that were defined under the 'property-datasoruce' profile. In the stack below, the jobDataSource bean is defined under the property-datasource profile.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobRepository': Cannot resolve reference to bean 'jobDataSource' while setting bean property 'dataSource'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'jobDataSource' is defined
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:329)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:107)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1391)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1132)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:461)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:192)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:589)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
at com.vmware.vfra.batch.inttest.WorkerControllerTest.setup(WorkerControllerTest.java:41)

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

The stack trace shows you still have the code from your example that instantiates a WebApplicationContext. If you look at my example you will see that the Spring configuration is loaded through @ContextConfiguration and that a WebApplicationContext is injected into the test via @Autowired. As a result the setup method does not create an ApplicationContext at all but rather uses the one that was created by the TestContext framework.

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

FYI: Rossen's example is missing a preceding slash for the classpath resource for the XML config file. It should look like this:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("/com/vmware/vfra/batch/web/servlet-context.xml")
@ActiveProfiles({"dev", "property-datasource"})
public class SuccessfulStepTest {

  @Autowired
  private WebApplicationContext wac;

  // ...

}

- Sam

@spring-projects-issues
Copy link
Collaborator Author

Jeff Holmes commented

Cool, I got it to work as you described...

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
@ActiveProfiles({"dev", "property-datasource"})
public class SuccessfulStepTest {

        // ...

	@Autowired
	private WebApplicationContext wac;

	@Before
	public void setup() {

		MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
				.alwaysExpect(status().isAccepted()).build();

		RestTemplate restTemplate = new RestTemplate(
				new MockMvcClientHttpRequestFactory(mockMvc));
		// used by MasterPartitionHandler
		restStepExecutionService.setRestTemplate(restTemplate);
		restStepExecutionService.setRestUrl("/job/{jobExecutionId}/step/{stepId}/name/{stepName}");
	}

        // ...
}

SuccessfulStepTest-context.xml:

...
        <import resource="classpath:/com/vmware/vfra/batch/web/servlet-context.xml" />
...

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Jeff,

We're glad to hear that works for you.

And thanks for providing us the feedback!

@spring-projects-issues spring-projects-issues added in: test Issues in the test module type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 3.2 RC2 milestone Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants