Chapter 3. Working with Spring Tests

This chapter covers the test module of Spring and the APIs used for unit and integration testing Spring applications. The following topics are covered here:

  • Spring's TestContext framework and SpringJUnit4ClassRunner
  • Spring profiles
  • Mocking environments with MockEnvironment and MockPropertySource
  • Mocking a JNDI lookup with SimpleNamingContextBuilder and ExpectedLookupTemplate
  • Testing with ReflectionTestUtils
  • Exploring Spring annotations for unit testing; the annotations covered are @ContextConfiguration, ApplicationContextInitializer, @WebAppConfiguration, @ContextHierarchy, @ActiveProfiles, @ProfileValueSourceConfiguration, @TestPropertySource, @DirtiesContext, @TestExecutionListeners, @IfProfileValue, @Timed, and @Repeat
  • Unit testing Spring MVC with MockHttpServletRequest, MockHttpSession, and ModelAndViewAssert, as well as Spring beans with request scope and Spring beans with session scope
  • Mocking a servlet container with MockMvc
  • Transaction management with @Transactional, @TransactionConfiguration, @Rollback, @BeforeTransaction, and @AfterTransaction

Exploring the TestContext framework

Spring's TestContext framework is a generic, annotation-driven framework for unit and integration testing. The framework's resources are located in the org.springframework.test.context package. This framework believes in the design paradigm "convention over configuration," which means that the framework provides reasonable defaults for every configuration; the user can still override the unconventional aspects through annotation-based configuration. The TestContext framework provides support for JUnit and TestNG, such as a custom JUnit runner that allows non-invasive POJO test classes.

The framework consists of two classes and three interfaces. The following are the classes:

  • TestContext: This class provides the context in which a test is executed. It also makes the context management and caching supports available for the test instance. To load the application context, the ContextLoader interface (or SmartContextLoader) is used.
  • TestContextManager: This class is the main entry point to the TestContext framework; it manages a single TestContext class and publishes events to all registered TestExecutionListener implementations at test execution points. These are the test execution points:
    • In static before class methods
    • In before test execution methods
    • During test instance preparation
    • In after test execution methods
    • In static after class methods

The following are the interfaces:

  • TestExecutionListener: The TestContextManager class publishes events to all the registered listeners. This interface defines the listener API to react to the published events.
  • ContextLoader: This interface loads ApplicationContext for the Spring integration tests.
  • SmartContextLoader: This interface is the extension of the ContextLoader interface and has been introduced in Spring 3.1. A SmartContextLoader interface processes resource locations, annotated classes, or context initializers. Also, it can set active bean profiles (@ActiveProfiles) and property sources in the context that it loads.

For each test, a TestContextManager class is being created. The TestContextManager class handles a TestContext class for the current test and updates the state of the TestContext class as the test progresses. For dependency injection, dirty checks, transactional support, and so on, the TestContextManager class delegates control to the TestExecutionListener implementations, which in turn implements the actual test execution by providing dependency injection, managing transactions, and so on.

The default TestExecutionListener implementations are registered in the following order:

  • ServletTestExecutionListener: This listener provides the Servlet API mocks for WebApplicationContext
  • DependencyInjectionTestExecutionListener: As the name suggests, this listener provides dependency injections for the test
  • DirtiesContextTestExecutionListener: This listener checks the context—whether any bean is dirtied or not during a test execution; it also handles the @DirtiesContext annotation
  • TransactionalTestExecutionListener: This provides transactional support
  • SqlScriptsTestExecutionListener: This executes SQL scripts configured via the @Sql annotation

The TestExecutionListener implementations externalize the reusable code to instrument tests. When we execute a TestExecutionListener implementation, we can reuse it across test class hierarchies and projects. Custom TestExecutionListener implementations can be registered for a test class and its subclasses via the @TestExecutionListeners annotation. If a custom TestExecutionListener implementation is registered via @TestExecutionListeners, the default listeners will not be registered. As a result, the developer has to manually declare all the default listeners in addition to any custom listeners. The following example demonstrates this style of configuration. Usually, we don't need a custom TestExecutionListener implementation unless we want to perform some custom logic before, during, or after the test method or test class execution. In the following section, we'll create a custom listener to print the test class and method names just before and after test execution.

Writing a custom TestExecutionListener interface

The following are the steps to create a custom TestExecutionListener implementation:

  1. Create a Java project, SpringTests.
  2. Create a SysOutTestExecutionListener Java class in the com.packt.listener package and implement the TestExecutionListener interface. All implemented methods print information about the test class or the test method. The TestExecutionListener listener can be reused with any Spring test class. The following is the implementation:
      public class SysOutTestExecutionListener implements
            TestExecutionListener {
      @Override public void afterTestClass(TestContext
            testContext) throws Exception {
        ApplicationContext ctx = 
            testContext.getApplicationContext();
        System.out.println("In afterTestClass for class = 
            "+testContext.getTestClass());
      }

    Note that you can get the application context, ctx, from the TestContext class to work with the Spring beans. Although I'm not doing any alterations to any bean configuration, you can do so from all the methods in a TestExecutionListner class, as shown here:

      @Override public void afterTestMethod(TestContext testContext) 
            throws Exception {
        System.out.println("In afterTestMethod for = 
            "+testContext.getTestMethod().getName());
      }
      @Override public void beforeTestClass(TestContext 
            testContext) throws Exception {
        System.out.println("In beforeTestClass for class = 
            "+testContext.getTestClass());
      }
      @Override public void beforeTestMethod(TestContext 
            testContext) throws Exception {
        System.out.println("In beforeTestMethod for = 
            "+testContext.getTestMethod().getName());
      }
      @Override
      public void prepareTestInstance(TestContext testContext)
            throws Exception {
        System.out.println("In prepareTestInstance for= 
            "+testContext.getTestInstance());
      }
    }

    The SysOutTestExecutionListener class implements five methods, namely, afterTestClass, beforeTestClass, afterTestMethod, beforeTestMethod, and prepareTestInstance. Each method accepts a TestContext object. A TextContext object can provide the test method, test class, test instance, application context, and the beans configured in the application context, and so on. We'll check the method execution sequence later.

  3. Create an empty applicationContext.xml file directly under the com.packt.listener package. You don't need to define any bean here. The following is the XML file:
      <?xml version="1.0" encoding="UTF-8"?>
      <beans 
        xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation=
      "http://www.springframework.org/schema/beans  
      http://www.springframework.org/schema/beans/spring- 
            beans.xsd">
      </beans>
  4. Create a test class to examine SysOutTestExecutionListener. The class details are as follows:
    package com.packt.listener;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.TestExecutionListeners;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:com/packt/listener/applicationContext.xml")
    @TestExecutionListeners({
        SysOutTestExecutionListener.class
    })
    public class TestExecutionListenerTest {
    
      @Test
      public void someTest() throws Exception {
        System.out.println("executing someTest");
      }
      
      @Test
      public void someOtherTest() throws Exception {
        System.out.println("executing someOtherTest");
      }
    }

    The class is annotated with @RunWith, @ContextConfiguration, and @ TestExecutionListeners. By annotating test classes with @RunWith(SpringJUnit4ClassRunner.class), we enable the class to get the benefits of Spring unit and integration tests, such as TestContext, the applicationContext loading, DI, transaction support, and so on.

    The @ContextConfiguration annotation loads the application context resource from the specified locations or the @Configuration annotated classes. In locations, we pass the XML configuration or the applicationContext XML location that can be loaded from the classpath.

    The @TestExecutionListeners annotation defines class-level metadata to configure which TestExecutionListener implementations should be registered with TestContextManager.

  5. The TestExecutionListenerTest class has two tests. When we execute the test class, the following output is displayed:
    In beforeTestClass for class = class com.packt.listener.TestExecutionListenerTest
    In prepareTestInstance for= com.packt.listener.TestExecutionListenerTest@548c491e
    In beforeTestMethod for = someOtherTest
    executing someOtherTest
    In afterTestMethod for = someOtherTest
    In prepareTestInstance for= com.packt.listener.TestExecutionListenerTest@5cd99967
    In beforeTestMethod for = someTest
    executing someTest
    In afterTestMethod for = someTest
    In afterTestClass for class = class com.packt.listener.TestExecutionListenerTest

    The beforeTestClass method is invoked first, and it is invoked only once for the test class; we can access the application context and beans using this method. The prepareTestMethod is invoked before any test method execution. We can get the test instance and prepare beans or initialize test-specific data from this method. The beforeTestMethod is executed after prepareTestMethod but before any test method execution, and then a test is executed. The afterTestMethod is executed after any test method execution. The afterTestClass method acts like the destructors in C++, and is invoked only once per class at the end of the last test method's afterTestMethod call.

    You might wonder what the difference is between JUnit 4's @before and @after and the TestExecutionListener methods. The answer is you can access TestContext in the TestExecutionListener methods but not in JUnit annotated methods, and TestExecutionListener logic can be shared with many tests but JUnit annotations are test class specific. For example, our SysOutTestExecutionListener logic can be shared with any test class; but if we annotate a test method with a JUnit 4 annotation, then that method cannot be shared with all the test classes unless they extend the class.

  6. If a custom TestExecutionListener class is registered via @TestExecutionListeners, the default listeners will not be registered. This forces the developer to manually declare all default listeners in addition to any custom listeners. The following listing demonstrates this style of configuration:
    @ContextConfiguration
    @TestExecutionListeners({
        SysOutTestExecutionListener.class,
        ServletTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        SqlScriptsTestExecutionListener.class
    
    })
    public class TestExecutionListenerTest {
       
    }
  7. To avoid the redeclaration of all default listeners, the mergeMode attribute of @TestExecutionListeners can be set to MergeMode.MERGE_WITH_DEFAULTS. The MERGE_WITH_DEFAULTS part indicates that locally declared listeners should be merged with the default listeners, as shown in the following listing:
    @ContextConfiguration
    @TestExecutionListeners(
        listeners = SysOutTestExecutionListener.class,
        mergeMode = MERGE_WITH_DEFAULTS
    
    )
    public class TestExecutionListenerTest {
    
    }

    The TextContext framework does not force you to extend any particular class or to implement a specific interface in order to configure the application context. Instead, configuration is achieved simply by declaring the @ContextConfiguration annotation at the class level.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.89.2