KEMBAR78
Spring 3.1 and MVC Testing Support | PDF
Spring 3.1 and MVC
        Testing Support
                     Sam Brannen
                         Swiftmind


                   Rossen Stoyanchev
                    SpringSource, VMware
SpringOne 2GX
October 28, 2011
Sam Brannen
Senior Software Consultant @ Swiftmind
Java developer with 13+ years' experience

Spring Framework Core Developer
Lead author of Spring in a Nutshell

Previous SpringSource dm Server™ developer

Presenter on Spring, Java, OSGi, and testing
Rossen Stoyanchev
Staff Engineer SpringSource, VMware
Spring MVC, Spring Web Flow committer

Teach and consult, Spring Projects

Spring Web course author
NYC area
Goals of the
      Presentation
Gain an overview of testing features
in Spring 3.1
Learn about the new Spring MVC
Test Support project
Agenda
Spring 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 TestContext
    Framework
Introduced in Spring 2.5
Revised in Spring 3.1
Unit and integration testing
Annotation-driven
Convention over Configuration
JUnit & TestNG
Spring & Unit Testing
POJO-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 utilities
  ReflectionTestUtils
  ModelAndViewAssert
Spring & Integration Testing
 ApplicationContext   management & caching

 Dependency injection of test instances

 Transactional test management
    with default rollback semantics

 SimpleJdbcTestUtils

 JUnit 3.8 support classes are deprecated as of
 Spring 3.0/3.1
Spring Test Annotations
Application Contexts
   @ContextConfiguration, @DirtiesContext

@TestExecutionListeners

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

Transactions
   @Transactional, @TransactionConfiguration, @Rollback,
   @BeforeTransaction, @AfterTransaction
Spring JUnit Annotations
  Testing Profiles
    groups, not bean definition
    profiles
    @IfProfileValue,
    @ProfileValueSourceConfiguration

  JUnit extensions
    @Timed, @Repeat
Using the TestContext
     Framework
Use the SpringJUnit4ClassRunner for
JUnit 4.5+
Instrument test class with
TestContextManager for TestNG

Extend one of the base classes
  Abstract(Transactional)[JUnit4|TestNG]Spri
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 package
public class OrderServiceTests {




    @Test
    public void testOrderService() { … }
}
Example: @POJO Test Class
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration // defaults to
// OrderServiceTests-context.xml in same package

public 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
@Transactional
public 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
@Transactional
public 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
@Transactional
public class OrderServiceTests {

    @Autowired
    private OrderService orderService;

    @BeforeTransaction
    public void verifyInitialDatabaseState() { … }

    @Before
    public void setUpTestDataWithinTransaction() { … }

    @Test
    public void testOrderService() { … }
}
Core Components
TestContext
Tracks context for current test

Delegates to a ContextLoader

Caches ApplicationContext
TestContextManager
Manages the TestContext

Signals events to listeners:
  before: before-class methods
  after: test instantiation
  before: before methods
  after: after methods
  after: after-class methods
TestExecutionListener             SPI
Reacts to test execution events
  Receives reference to current TestContext

Out of the box:
  DependencyInjectionTestExecutionListener
  DirtiesContextTestExecutionListener
  TransactionalTestExecutionListener
TestExecutionListener
TEL: Prepare Instance
TEL: Befores and Afters
ContextLoader          SPI
Strategy for loading application contexts
   from resource locations

Out of the box:
  GenericXmlContextLoader
  GenericPropertiesContextLoader
ContextLoader   2.5
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           SPI
Supersedes ContextLoader

Strategy for loading application
contexts
From @Configuration classes or
resource locations
Supports environment profiles
Implementations
GenericXmlContextLoader

GenericPropertiesContextLoader

AnnotationConfigContextLoader

DelegatingSmartContextLoader
ContextLoader   2.5
ContextLoader   3.1
Testing with
@Configuration Classes
@ContextConfiguration
   accepts a new classes attribute...
@ContextConfiguration(
    classes={DataAccessConfig.class,
             ServiceConfig.class})
First, a review with XML config for
           comparison...
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)
@ContextConfiguration
public 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 new
  AnnotationConfigContextLoader...
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)
@ContextConfiguration
public class OrderServiceTest {




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

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




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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public 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 to
Java
  using @Configuration and @Bean

Otherwise, the test remains
unchanged
But what if we don't want a static
    inner @Configuration class?
Just externalize the config...
OrderServiceConfig.java

@Configuration
public 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 + XML
 Q: How can we combine
 @Configuration classes with XML
 config?
 A: Choose one as the entry point.

 That's how it works in production
 anyway
Importing Configuration
 In XML:
   include @Configuration classes via
   component scanning
   or define them as normal Spring
   beans

 In an @Configuration class:
   use @ImportResource to import XML
   config files
Testing with
Environment Profiles
@ActiveProfiles
  To activate bean definition profiles in tests...


Annotate a test class with
@ActiveProfiles

Supply a list of profiles to be
activated for the test
Can be used with @Configuration
classes and XML config
Let's look at an example with XML
              config...
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

@Configuration
public 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
ApplicationContext
      Caching
Until Spring 3.1
application contexts were cached
but using only resource locations for
              the key.
Now there are different
   requirements...
New Key Generation Algorithm
The context cache key generation algorithm has been updated to include...


     locations (from @ContextConfiguration)

     classes (from @ContextConfiguration)

     contextLoader (from @ContextConfiguration)

     activeProfiles (from @ActiveProfiles)
Summary
The Spring TestContext Framework simplifies
integration testing of Spring-based
applications
Spring 3.1 provides first-class testing support
for:
   @Configuration classes
   Environment profiles

See the Testing with @Configuration Classes
and Profiles blog for further insight
Spring MVC Test
    Support
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
@Test
public void testSave() {
    Account account = new Account();
    BindingResult result =
        new BeanPropertyBindingResult(account, "account");




}
Example
@Test
public void testSave() {

    Account account = new Account();
    BindingResult result =
        new BeanPropertyBindingResult(account, "account");
    AccountManager mgr = createMock(AccountManager.class);
    mgr.saveOrUpdate(account);
    replay(mgr);




}
Example
@Test
public 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
@Test
public 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
@Test
public void testSave() {
    Account account = new Account();
    BindingResult result =
        new BeanPropertyBindingResult(account, "account");

    result.reject("error.code", "default message")




}
Example With Failure
@Test
public 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
@Test
public 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 Tested
  Request mappings

    Binding errors

   Type conversion

         Etc.
Integration Test?
      Selenium

      JWebUnit

        etc.
It Requires...
A running servlet container

  More time to execute

 More effort to maintain

   Server is a black box
Actually...
it's an end-to-end test
the only way to verify...
         Client-side behavior

Interaction with other server instances

          Redis, Rabbit, etc.
We'd also 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

   Have confidence in @MVC code

      During end-to-end tests
Spring MVC Test
         Built 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 Approach
  Re-use controller test fixtures

I.e. mocked services & repositories

Invoke @Controller classes through
      @MVC infrastructure
Example
String 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 Covers
Mock request/response from spring-test

    DispatcherServlet   replacement

   Multiple ways to initialize MVC
            infrastructure

    Save results for expectations
Ways To Initialize MVC
   Infrastructure
From Existing XML Config
// XML config
MockMvc mockMvc =
    xmlConfigSetup("classpath:appContext.xml")
      .activateProfiles(..)
      .configureWebAppRootDir(warDir, false)
      .build();
From Existing Java Config
// Java config
MockMvc mockMvc =
    annotationConfigSetup(WebConfig.class)
      .activateProfiles(..)
      .configureWebAppRootDir(warDir, false)
      .build();
Manually
MockMvc mockMvc =
    standaloneSetup(new PersonController())
      .setMessageConverters(..)
      .setValidator(..)
      .setConversionService(..)
      .addInterceptors(..)
      .setViewResolvers(..)
      .setLocaleResolver(..)
      .build();
About Manual Setup
MVC components instantiated directly

   Not looked up in Spring context

       Focused, readable tests

But must verify MVC config separately
TestContext            Framework Example
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
        locations={"/org/example/servlet-context.xml"})
public class TestContextTests {
    @Autowired
    private WebApplicationContext wac;
    @Before
    public void setup() {
      MockMvc mockMvc =
          MockMvcBuilders.webApplicationContextSetup(this.wac)
            .build();
    }
}
TestContext       Framework
 Caches loaded Spring configuration

     Potentially across all tests!
However...
WebApplicationContext   not supported yet

    To be supported in Spring 3.2
In the mean time...
You can use a custom ContextLoader

 Example exists in spring-test-mvc
Step 1
  Add static imports

   MockMvcBuilders.*

MockMvcRequestBuilders.*

MockMvcResultActions.*
Easy to remember...
       MockMvc*
Also in Eclipse...
Add MockMvc* classes in Preferences

   Java -> Editor -> Favorites

       Helps content assist
Step 2
              Initialize MVC infrastructure

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




// ...
Step 3
                     Build 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 4
                   Add 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 5
                         Debug

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());




// ...
Demo
https://github.com/SpringSource/spring-test-mvc



                Sample Tests:

  org.springframework.test.web.server.samples
Client-Side Example
RestTemplate restTemplate = ... ;

MockRestServiceServer.createServer(restTemplate)
    .expect(requestTo(String))
    .andExpect(method(HttpMethod))
    .andExpect(body(String))
    .andExpect(headerContains(String, String))
    .andRespond(withResponse(String, HttpHeaders));
Project Availability
github.com/SpringSource/spring-test-mvc

        Request for feedback!

           Send comments

             Pull requests
Nightly Snapshot Available
<repository>
    <id>org.springframework.maven.snapshot</id>
    <name>Spring Maven Snapshot Repository</name>
    <url>http://maven.springframework.org/snapshot</url>
    <releases>
        <enabled>false</enabled>
    </releases>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>
In Closing ...
This Presentation
Source:
https://github.com/rstoyanchev/spring-31-and-mvc-
testing-support
Slides:
http://rstoyanchev.github.com/spring-31-and-mvc-
test
Resources for Spring MVC Test
Project Home:
https://github.com/SpringSource/spring-test-mvc
Sample Tests:
org.springframework.test.web.server.samples
Resources for Core Spring
Spring Framework:
http://www.springsource.org/spring-core
Reference Manual: Spring 3.1 RC1

Forums: http://forum.springframework.org
JIRA: https://jira.springsource.org

Spring in a Nutshell … available in 2012
Q&A
  Sam Brannen
  Web: Swiftmind.com
 Twitter: @sam_brannen


Rossen Stoyanchev
 Web: SpringSource.com
   Twitter: @rstoya05

Spring 3.1 and MVC Testing Support