spring 3.1 and mvc testing support - 4developers

Post on 10-May-2015

1.743 Views

Category:

Technology

9 Downloads

Preview:

Click to see full reader

DESCRIPTION

Please note that this presentation is an abridged version of the one given by Rossen Stoyanchev and me at SpringOne 2GX 2012. This session will give attendees an overview of the new testing features in Spring 3.1 as well the new Spring MVC test support. Sam Brannen will demonstrate how to use the Spring TestContext Framework to write integration tests for Java-based Spring configuration using @Configuration classes. He'll then compare and contrast this approach with XML-based configuration and follow up with a discussion of the new testing support for bean definition profiles. Next, attendees will see how testing server-side code with annotated controllers and client-side code with the RestTemplate just got a whole lot easier with the new Spring MVC test support. Come to this session to see these new Spring testing features in action.

TRANSCRIPT

Spring 3.1 and MVCTesting Support

Sam BrannenSwiftmind

4Developers18 April 2012

Sam BrannenSpring & Java Consultant @ Swiftmind

Developing Java for over 14 years

Spring Framework Core Committer

Spring Trainer

Lead author of “Spring in a Nutshell”

Twitter: @sam_brannen

Goals of thePresentation

Gain an overview of testing featuresin Spring 3.1

Learn about the new Spring MVCTest Support project

AgendaSpring TestContext Framework

Testing with @Configuration Classes

Testing with Environment Profiles

Spring MVC Test Support

Q&A

Show of Hands...JUnit / TestNG

Spring 2.5 / 3.0 / 3.1

Integration testing with Spring

Spring TestContext Framework

Spring MVC

Spring MVC Test Support

Spring TestContextFramework

Introduced in Spring 2.5

Revised in Spring 3.1

Unit and integration testing

Annotation-driven

Convention over Configuration

JUnit & TestNG

Spring & Unit TestingPOJO-based programming model

Program to interfaces

IoC / Dependency Injection

Out-of-container testability

Testing mocks/stubs for various APIs: Servlet,Portlet, JNDI

General purpose testing utilitiesReflectionTestUtilsModelAndViewAssert

Spring & Integration TestingApplicationContext management & caching

Dependency injection of test instances

Transactional test managementwith default rollback semantics

SimpleJdbcTestUtils

JUnit 3.8 support classes are deprecated as ofSpring 3.0/3.1

Spring Test AnnotationsApplication Contexts

@ContextConfiguration, @DirtiesContext

@TestExecutionListeners

Dependency Injection@Autowired, @Qualifier, @Inject, …

Transactions@Transactional, @TransactionConfiguration, @Rollback,@BeforeTransaction, @AfterTransaction

Using the TestContextFramework

Use the SpringJUnit4ClassRunner forJUnit 4.5+

Instrument test class withTestContextManager for TestNG

Extend one of the base classesAbstract(Transactional)[JUnit4|TestNG]SpringContextTests

Example: @POJO Test Class

public class OrderServiceTests {

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)

public class OrderServiceTests {

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration

public class OrderServiceTests {

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration // defaults to// OrderServiceTests-context.xml in same packagepublic class OrderServiceTests {

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration // defaults to// OrderServiceTests-context.xml in same packagepublic class OrderServiceTests { @Autowired private OrderService orderService;

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration // defaults to// OrderServiceTests-context.xml in same package@Transactionalpublic class OrderServiceTests { @Autowired private OrderService orderService;

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration // defaults to// OrderServiceTests-context.xml in same package@Transactionalpublic class OrderServiceTests { @Autowired private OrderService orderService; @BeforeTransaction public void verifyInitialDatabaseState() { … }

@Test public void testOrderService() { … }}

Example: @POJO Test Class@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration // defaults to// OrderServiceTests-context.xml in same package@Transactionalpublic class OrderServiceTests { @Autowired private OrderService orderService; @BeforeTransaction public void verifyInitialDatabaseState() { … } @Before public void setUpTestDataWithinTransaction() { … } @Test public void testOrderService() { … }}

Core Components

TestContextTracks context for current test

Delegates to a ContextLoader

Caches ApplicationContext

TestContextManagerManages the TestContext

Signals events to listeners...

TestExecutionListener SPIReacts to test execution events

Receives reference to current TestContext

Out of the box:DependencyInjectionTestExecutionListenerDirtiesContextTestExecutionListenerTransactionalTestExecutionListener

ContextLoader SPIStrategy for loading application contexts

from resource locations

Putting it all together

New in Spring 3.1

Support for ...

testing with @Configuration classes

and

testing with environment profiles

made possible by ...

SmartContextLoader

AnnotationConfigContextLoader

DelegatingSmartContextLoader

updated context cache key generation

SmartContextLoader SPISupersedes ContextLoader

Strategy for loading applicationcontexts

From @Configuration classes orresource locations

Supports environment profiles

ImplementationsGenericXmlContextLoader

GenericPropertiesContextLoader

AnnotationConfigContextLoader

DelegatingSmartContextLoader

ContextLoader 3.1

Testing with@Configuration Classes

@ContextConfigurationaccepts a new classes attribute...

@ContextConfiguration( classes={DataAccessConfig.class, ServiceConfig.class})

First, a review with XML config forcomparison...

OrderServiceTest-context.xml

<?xml version="1.0" encoding="UTF-8"?><beans ...> <!-- will be injected into OrderServiceTest --> <bean id="orderService" class="com.example.OrderServiceImpl"> <!-- set properties, etc. --> </bean> <!-- other beans --></beans>

OrderServiceTest.java

package com.example;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfigurationpublic class OrderServiceTest { @Autowired private OrderService orderService; @Test public void testOrderService() { // test the orderService }}

Let's rework that example to use a@Configuration class and the newAnnotationConfigContextLoader...

OrderServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)

public class OrderServiceTest {

@Autowired private OrderService orderService; // @Test methods ...}

OrderServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration( loader=AnnotationConfigContextLoader.class)public class OrderServiceTest {

@Autowired private OrderService orderService; // @Test methods ...}

OrderServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfigurationpublic class OrderServiceTest {

@Autowired private OrderService orderService; // @Test methods ...}

OrderServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfigurationpublic class OrderServiceTest { @Configuration static class Config {

} @Autowired private OrderService orderService; // @Test methods ...}

OrderServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfigurationpublic class OrderServiceTest { @Configuration static class Config { @Bean // will be injected into OrderServiceTest public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); // set properties, etc. return orderService; } } @Autowired private OrderService orderService; // @Test methods ...}

What's changed?No XML

Bean definitions are converted toJava

using @Configuration and @Bean

Otherwise, the test remainsunchanged

But what if we don't want a staticinner @Configuration class?

Just externalize the config...

OrderServiceConfig.java

@Configurationpublic class OrderServiceConfig { @Bean // will be injected into OrderServiceTest public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); // set properties, etc. return orderService; }}

And reference the config classes in@ContextConfiguration...

OrderServiceConfig.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=OrderServiceConfig.class)public class OrderServiceTest { @Autowired private OrderService orderService; @Test public void testOrderService() { // test the orderService }}

@Configuration + XMLQ: How can we combine@Configuration classes with XMLconfig?

A: Choose one as the entry point.

That's how it works in productionanyway

Importing ConfigurationIn XML:

include @Configuration classes viacomponent scanningor define them as normal Springbeans

In an @Configuration class:use @ImportResource to import XMLconfig files

Testing withEnvironment Profiles

@ActiveProfilesTo activate bean definition profiles in tests...

Annotate a test class with@ActiveProfiles

Supply a list of profiles to beactivated for the test

Can be used with @Configurationclasses and XML config

Let's look at an example with XMLconfig...

app-config.xml (1/2)

<beans ... > <bean id="transferService" class="com.example.DefaultTransferService"> <constructor-arg ref="accountRepository"/> <constructor-arg ref="feePolicy"/> </bean> <bean id="accountRepository" class="com.example.JdbcAccountRepository"> <constructor-arg ref="dataSource"/> </bean> <bean id="feePolicy" class="com.example.ZeroFeePolicy"/> <!-- dataSource --></beans>

app-config.xml (2/2)

<beans ... > <!-- transferService, accountRepository, feePolicy -->

</beans>

app-config.xml (2/2)

<beans ... > <!-- transferService, accountRepository, feePolicy --> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans>

</beans>

app-config.xml (2/2)

<beans ... > <!-- transferService, accountRepository, feePolicy --> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:schema.sql"/> <jdbc:script location="classpath:test-data.sql"/> </jdbc:embedded-database> </beans></beans>

TransferServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)

public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService }}

TransferServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("/app-config.xml")public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService }}

TransferServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("/app-config.xml")@ActiveProfiles("dev")public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService }}

And now an example with@Configuration classes

TransferServiceConfig.java

@Configurationpublic class TransferServiceConfig { @Autowired DataSource dataSource; @Bean public TransferService transferService() { return new DefaultTransferService(accountRepository(), feePolicy()); } @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public FeePolicy feePolicy() { return new ZeroFeePolicy(); }}

}

JndiDataConfig.java

@Configuration@Profile("production")public class JndiDataConfig { @Bean public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}

StandaloneDataConfig.java

@Configuration@Profile("dev")public class StandaloneDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); }}

And finally the test class...

TransferServiceTest.java

package com.bank.service;@RunWith(SpringJUnit4ClassRunner.class)

public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService }}

TransferServiceTest.java

package com.bank.service;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration( classes={ TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class})public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService }}

TransferServiceTest.java

package com.bank.service;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration( classes={ TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class})@ActiveProfiles("dev")public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService }}

Active Profile Inheritance@ActiveProfiles supports inheritance as well

Via the inheritProfiles attribute

See Javadoc for an example

ApplicationContextCaching

Until Spring 3.1

application contexts were cached

but using only resource locations forthe key.

Now there are differentrequirements...

New Key Generation AlgorithmThe context cache key generation algorithm has been updated to include...

locations (from @ContextConfiguration)

classes (from @ContextConfiguration)

contextLoader (from @ContextConfiguration)

activeProfiles (from @ActiveProfiles)

SummaryThe Spring TestContext Framework simplifiesintegration testing of Spring-basedapplications

Spring 3.1 provides first-class testing supportfor:

@Configuration classesEnvironment profiles

See the Testing with @Configuration Classesand Profiles blog for further insight

Spring MVC TestSupport

How Do You Test an@Controller?

@Controller@RequestMapping("/accounts")public class AccountController { // ... @ModelAttribute public Account getAccount(String number) { return this.accountManager.getAccount(number); } @RequestMapping(method = RequestMethod.POST) public String save(@Valid Account account, BindingResult result) { if (result.hasErrors()) { return "accounts/edit"; } this.accountManager.saveOrUpdate(account); return "redirect:accounts"; }}

Unit Test?Create controller instance

Mock or stub services & repositories

Example@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account");

}

Example@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account"); AccountManager mgr = createMock(AccountManager.class); mgr.saveOrUpdate(account); replay(mgr);

}

Example@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account"); AccountManager mgr = createMock(AccountManager.class); mgr.saveOrUpdate(account); replay(mgr); AccountController contrlr = new AccountController(mgr); String view = contrlr.save(account, result);

}

Example@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account"); AccountManager mgr = createMock(AccountManager.class); mgr.saveOrUpdate(account); replay(mgr); AccountController contrlr = new AccountController(mgr); String view = contrlr.save(account, result); assertEquals("redirect:accounts", view); verify(mgr);}

Example With Failure@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account"); result.reject("error.code", "default message")

}

Example With Failure@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account"); result.reject("error.code", "default message") AccountManager mgr = createMock(AccountManager.class); replay(mgr);

}

Example With Failure@Testpublic void testSave() { Account account = new Account(); BindingResult result = new BeanPropertyBindingResult(account, "account"); result.reject("error.code", "default message") AccountManager mgr = createMock(AccountManager.class); replay(mgr); AccountController contrlr = new AccountController(mgr); String view = contrlr.save(account, result); assertEquals("accounts/edit", view); verify(mgr);}

Not Fully TestedRequest mappings?

Binding errors?

Type conversion?

etc.

Integration Test?Selenium

JWebUnit

etc.

But that requires...A running servlet container

More time to execute

More effort to maintain

Server is a black box

And actually...

it's an end-to-end test

and the only way toverify...

Client-side behavior

Interaction with other server instances

Redis, Rabbit, etc.

Ideally we'd like to...Test controllers once!

Fully & quickly

Execute tests often

In summary...We can't replace the need for end-to-

end tests.

But we can minimize errors

and gain confidence in @MVC code

during end-to-end tests

Spring MVC TestBuilt on spring-test

No Servlet container

Drives Spring MVC infrastructure

Both server & client-side test support (i.e. RestTemplate code)

Inspired by spring-ws-test

The ApproachRe-use controller test fixtures

i.e. mocked services & repositories

Invoke @Controller classes through@MVC infrastructure

ExampleString contextLoc = "classpath:appContext.xml";String warDir = "src/main/webapp";MockMvc mockMvc = xmlConfigSetup(contextLoc) .configureWebAppRootDir(warDir, false) .build();mockMvc.perform(post("/persons")) .andExpect(response().status().isOk()) .andExpect(response().forwardedUrl("/add.jsp")) .andExpect(model().size(1)) .andExpect(model().hasAttributes("person"));

Under the CoversMock request/response from spring-test

DispatcherServlet replacement

Multiple ways to initialize MVCinfrastructure

Save results for expectations

Ways to Initialize MVCInfrastructure

From Existing XML Config// XML configMockMvc mockMvc = xmlConfigSetup("classpath:appContext.xml") .activateProfiles(..) .configureWebAppRootDir(warDir, false) .build();

From Existing Java Config// Java configMockMvc mockMvc = annotationConfigSetup(WebConfig.class) .activateProfiles(..) .configureWebAppRootDir(warDir, false) .build();

Manually MockMvc mockMvc = standaloneSetup(new PersonController()) .setMessageConverters(..) .setValidator(..) .setConversionService(..) .addInterceptors(..) .setViewResolvers(..) .setLocaleResolver(..) .build();

TestContext Framework Example@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("/org/example/servlet-context.xml")public class TestContextTests { @Autowired private WebApplicationContext wac; @Before public void setup() { MockMvc mockMvc = MockMvcBuilders.webApplicationContextSetup(this.wac) .build(); }}

TestContext FrameworkCaches loaded Spring configuration

Potentially across all tests!

However...WebApplicationContext is not yet

supported

To be supported in Spring 3.2

How to Use Spring MVCTest

Step 1Add static imports

MockMvcBuilders.*

MockMvcRequestBuilders.*

MockMvcResultActions.*

Easy to remember...MockMvc*

Step 2Initialize MVC infrastructure

String contextLoc = "classpath:appContext.xml";String warDir = "src/main/webapp";MockMvc mockMvc = xmlConfigSetup("classpath:appContext.xml") .configureWebAppRootDir(warDir, false) .build()

// ...

Step 3Build Request

String contextLoc = "classpath:appContext.xml";String warDir = "src/main/webapp";MockMvc mockMvc = xmlConfigSetup("classpath:appContext.xml") .configureWebAppRootDir(warDir, false) .build()mockMvc.perform(get("/").accept(MediaType.APPLICATION_XML))

// ...

Step 4Add Expectations

String contextLoc = "classpath:appContext.xml";String warDir = "src/main/webapp";MockMvc mockMvc = xmlConfigSetup("classpath:appContext.xml") .configureWebAppRootDir(warDir, false) .build()mockMvc.perform(get("/").accept(MediaType.APPLICATION_XML)) .andExpect(response().status().isOk()) .andExpect(response().contentType(MediaType)) .andExpect(response().content().xpath(String).exists()) .andExpect(response().redirectedUrl(String)) .andExpect(model().hasAttributes(String...));

// ...

Step 5Debug

String contextLoc = "classpath:appContext.xml";String warDir = "src/main/webapp";MockMvc mockMvc = xmlConfigSetup("classpath:appContext.xml") .configureWebAppRootDir(warDir, false) .build()mockMvc.perform(get("/").accept(MediaType.APPLICATION_XML)) .andPrint(toConsole());

// ...

Client-Side ExampleRestTemplate restTemplate = ... ;MockRestServiceServer.createServer(restTemplate) .expect(requestTo(String)) .andExpect(method(HttpMethod)) .andExpect(body(String)) .andExpect(headerContains(String, String)) .andRespond(withResponse(String, HttpHeaders));

Project Availabilitygithub.com/SpringSource/spring-test-mvc

Request for feedback!

Send comments

Pull requests

In Closing ...

This PresentationSource:https://github.com/rstoyanchev/spring-31-and-mvc-testing-support

Slides:http://rstoyanchev.github.com/spring-31-and-mvc-test

Resources for Spring MVC TestProject Home:https://github.com/SpringSource/spring-test-mvc

Sample Tests:org.springframework.test.web.server.samples

Resources for Core SpringSpring Framework:http://www.springsource.org/spring-core

Reference Manual: Spring 3.1.x

Forums: http://forum.springframework.org

JIRA: https://jira.springsource.org

Spring in a Nutshell … available in 2012

Q&A

Sam BrannenWeb: Swiftmind.com

Twitter: @sam_brannenSlideshare: sbrannen

top related