From 80fde6cd3a2dec89d74a292436e4024b29d759c9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 21 Jan 2011 00:23:34 +0000 Subject: [PATCH] [SPR-7849] revised the testing chapter based on internal review and new insight. git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3914 50f2f4bb-b051-0410-bef5-90022cba6387 --- spring-framework-reference/src/testing.xml | 311 +++++++++++---------- 1 file changed, 157 insertions(+), 154 deletions(-) diff --git a/spring-framework-reference/src/testing.xml b/spring-framework-reference/src/testing.xml index 26f74d3b6c5..0190fb22d6a 100644 --- a/spring-framework-reference/src/testing.xml +++ b/spring-framework-reference/src/testing.xml @@ -11,8 +11,8 @@ chapter focuses on the value-add of the IoC principle to unit testing and on the benefits of Spring Framework integration testing. - (A thorough treatment of testing in the enterprise is beyond the scope of - this chapter.) + (A thorough treatment of testing in the enterprise is beyond the + scope of this chapter.)
@@ -159,11 +159,11 @@ The Spring Framework provides first-class support for integration testing in the spring-test module. - The name of the actual jar file might include the release + The name of the actual JAR file might include the release version and might also be in the long org.springframework.test form, depending on where you got it from (see - the section on Dependency + the section on Dependency Management for an explanation). This library includes the org.springframework.test package, which contains valuable classes for integration testing with a Spring @@ -248,12 +248,12 @@ By default, once loaded, the configured ApplicationContext is reused for each - test. Thus the setup cost is incurred only once (per test fixture), + test. Thus the setup cost is incurred only once (per test suite), and subsequent test execution is much faster. In the unlikely case that a test corrupts the application context and requires reloading — for example, by modifying a bean definition or the state of an - application object — a Spring testing support mechanism causes the - test fixture to reload the configurations and rebuilds the application + application object — the TestContext framework can be configured + to reload the configurations and rebuild the application context before executing the next test. See context management and caching with the As an example, consider the scenario where we have a class, - HibernateTitleDao, that performs data access + HibernateTitleRepository, that performs data access logic for say, the Title domain object. We want to write integration tests that test all of the following areas: @@ -284,7 +284,7 @@ The Spring configuration: basically, is everything related to the configuration of the - HibernateTitleDao bean correct and + HibernateTitleRepository bean correct and present? @@ -295,7 +295,7 @@ - The logic of the HibernateTitleDao: + The logic of the HibernateTitleRepository: does the configured instance of this class perform as anticipated? @@ -521,7 +521,7 @@ public void testProcessWhichDirtiesAppCtx() { TestExecutionListeners should be registered with the TestContextManager. Typically, @TestExecutionListeners - are used in conjunction with + is used in conjunction with @ContextConfiguration. @ContextConfiguration @@ -593,7 +593,7 @@ public void testProcessWithoutRollback() { Indicates that the annotated public void method should be executed before a transaction is started for test methods configured to run within a transaction - through the @Transactional + via the @Transactional annotation. @BeforeTransaction @@ -609,7 +609,7 @@ public void beforeTransaction() { Indicates that the annotated public void method should be executed after a transaction has ended for test methods configured to run within a transaction - through the @Transactional + via the @Transactional annotation. @AfterTransaction @@ -870,7 +870,7 @@ public void testProcessRepeatedly() { In addition to generic testing infrastructure, the TestContext framework provides explicit support for JUnit 3.8.2, JUnit 4.5+, and TestNG 5.12 in the form of abstract support classes. - For JUnit 4.5+, the framework also provides a custom + For JUnit 4.5+, the framework also provides a custom JUnit Runner that allows one to write test classes that are not required to extend a particular class hierarchy. @@ -1017,8 +1017,8 @@ public class MyTest { class level. If your test class does not explicitly declare application context resource locations, the configured ContextLoader determines how - and whether to load a context from a default set of locations. For - example, GenericXmlContextLoader , which is the + and whether to load a context from a default location. For + example, GenericXmlContextLoader, which is the default ContextLoader, generates a default location based on the name of the test class. If your class is named com.example.MyTest, @@ -1036,22 +1036,25 @@ public class MyTest { } If the default location does not suit your needs, you can - configure explicitly the locations attribute of + explicitly configure the locations attribute of @ContextConfiguration with an array that contains the resource locations of XML configuration metadata (assuming an XML-capable ContextLoader - has been configured) — typically in the classpath - — used to configure the application. (See the following code example.) - This location will be the same, or nearly the same, as the list of - configuration locations specified in web.xml or - other deployment configuration. Alternatively, you can implement and - configure your own custom + has been configured, which is the default). A plain path, for + example "context.xml", will be treated as a + classpath resource from the same package in which the test class + is defined. A path starting with a slash is treated as a fully qualified + classpath location, for example "/org/example/config.xml". + A path which represents a URL (i.e., a path prefixed with + classpath:, file:, + http:, etc.) will be used as is. + Alternatively, you can implement and configure your own custom ContextLoader. @RunWith(SpringJUnit4ClassRunner.class) // ApplicationContext will be loaded from "/applicationContext.xml" and "/applicationContext-test.xml" // in the root of the classpath -@ContextConfiguration({"/applicationContext.xml", "/applicationContext-test.xml"}) +@ContextConfiguration(locations={"/applicationContext.xml", "/applicationContext-test.xml"}) public class MyTest { // class body... } @@ -1062,7 +1065,9 @@ public class MyTest { to configure a custom ContextLoader, you can omit the declaration of the locations attribute name and declare the resource locations by using the - shorthand format demonstrated in the following example. + shorthand format demonstrated in the following example. + + @ContextConfiguration also supports a boolean inheritLocations attribute that denotes whether resource locations from superclasses should be @@ -1073,8 +1078,7 @@ public class MyTest { appended to the list of resource locations defined by an annotated superclass. Thus, subclasses have the option of extending the list of resource locations. In the - following example, the - ApplicationContext for + following example, the ApplicationContext for ExtendedTest is loaded from "/base-context.xml" and "/extended-context.xml", in that order. Beans defined in "/extended-context.xml" may therefore override @@ -1096,22 +1100,21 @@ public class ExtendedTest extends BaseTest { If inheritLocations is set to false, the resource locations for the annotated - class shadows and effectively replaces any resource locations defined + class shadow and effectively replace any resource locations defined by a superclass. By default, once loaded, the configured ApplicationContext is reused for each - test. Thus the setup cost is incurred only once (per test fixture), + test. Thus the setup cost is incurred only once (per test suite), and subsequent test execution is much faster. In the unlikely case - that a test dirties (modifies) - the application context, requiring reloading — for example, by - changing a bean definition or the state of an application object — - you can annotate your test method with - @DirtiesContext (assuming + that a test corrupts the application context and requires reloading — + for example, by modifying a bean definition or the state of an + application object — you can annotate your test class or test + method with @DirtiesContext (assuming DirtiesContextTestExecutionListener has been - configured, which is the default) to cause the test fixture to reload + configured, which is the default). This instructs Spring to reload the configurations and rebuild the application context before - executing the next test. + executing the next test.
@@ -1119,12 +1122,10 @@ public class ExtendedTest extends BaseTest { When you configure the DependencyInjectionTestExecutionListener — - which is configured by default through the - @TestExecutionListeners annotation — - the dependencies of your test instances are - injected from beans in the application context - you configured through - @ContextConfiguration by setter + which is configured by default — the dependencies of your + test instances are injected from beans in the + application context that you configured with + @ContextConfiguration. You may use setter injection, field injection, or both, depending on which annotations you choose and whether you place them on setter methods or fields. For consistency with the annotation support introduced in Spring 2.5, you @@ -1155,7 +1156,7 @@ public class ExtendedTest extends BaseTest { name. Alternatively, if your test class has access to its ApplicationContext, you can perform an explicit lookup by using (for example) a call to - applicationContext.getBean("titleDao"). A + applicationContext.getBean("titleRepository"). A third option is to use @Autowired in conjunction with @Qualifier. @@ -1169,13 +1170,13 @@ public class ExtendedTest extends BaseTest { DependencyInjectionTestExecutionListener.class from the list of listeners. - Consider the scenario of a class, - HibernateTitleDao, as outlined in the Goals section. (We will look at - the application context configuration after all sample code listings.) - A JUnit 4-based implementation of the test class itself uses - @Autowired for field injection. - + Consider the scenario of testing a + HibernateTitleRepository class, as outlined in the Goals section. The next four + code listings demonstrate the use of @Autowired + and @Resource on fields and + setter methods. The application context configuration is presented + after all sample code listings. The dependency injection behavior in the following code @@ -1191,90 +1192,99 @@ public class ExtendedTest extends BaseTest { example. + The first code listing shows a JUnit 4-based implementation + of the test class that uses @Autowired + for field injection. + @RunWith(SpringJUnit4ClassRunner.class) // specifies the Spring configuration to load for this test fixture -@ContextConfiguration("daos.xml") -public final class HibernateTitleDaoTests { +@ContextConfiguration("repository-config.xml") +public class HibernateTitleRepositoryTests { // this instance will be dependency injected by type @Autowired - private HibernateTitleDao titleDao; + private HibernateTitleRepository titleRepository; - public void testLoadTitle() throws Exception { - Title title = this.titleDao.loadTitle(new Long(10)); + @Test + public void loadTitle() { + Title title = titleRepository.loadTitle(new Long(10)); assertNotNull(title); } } Alternatively, you can configure the class to use - @Autowired for setter injection. + @Autowired for setter injection as + seen below. @RunWith(SpringJUnit4ClassRunner.class) // specifies the Spring configuration to load for this test fixture -@ContextConfiguration("daos.xml") -public final class HibernateTitleDaoTests { +@ContextConfiguration("repository-config.xml") +public class HibernateTitleRepositoryTests { // this instance will be dependency injected by type - private HibernateTitleDao titleDao; + private HibernateTitleRepository titleRepository; @Autowired - public void setTitleDao(HibernateTitleDao titleDao) { - this.titleDao = titleDao; + public void setTitleRepository(HibernateTitleRepository titleRepository) { + this.titleRepository = titleRepository; } - public void testLoadTitle() throws Exception { - Title title = this.titleDao.loadTitle(new Long(10)); + @Test + public void loadTitle() { + Title title = titleRepository.loadTitle(new Long(10)); assertNotNull(title); } } - Here is an example of @Resource + The following is an example of using @Resource for field injection. @RunWith(SpringJUnit4ClassRunner.class) // specifies the Spring configuration to load for this test fixture -@ContextConfiguration("daos.xml") -public final class HibernateTitleDaoTests { +@ContextConfiguration("repository-config.xml") +public class HibernateTitleRepositoryTests { // this instance will be dependency injected by name @Resource - private HibernateTitleDao titleDao; + private HibernateTitleRepository titleRepository; - public void testLoadTitle() throws Exception { - Title title = this.titleDao.loadTitle(new Long(10)); + @Test + public void loadTitle() { + Title title = titleRepository.loadTitle(new Long(10)); assertNotNull(title); } } - Here is an example of @Resource + Here is an example of using @Resource for setter injection. @RunWith(SpringJUnit4ClassRunner.class) // specifies the Spring configuration to load for this test fixture -@ContextConfiguration("daos.xml") -public final class HibernateTitleDaoTests { +@ContextConfiguration("repository-config.xml") +public class HibernateTitleRepositoryTests { // this instance will be dependency injected by name - private HibernateTitleDao titleDao; + private HibernateTitleRepository titleRepository; @Resource - public void setTitleDao(HibernateTitleDao titleDao) { - this.titleDao = titleDao; + public void setTitleRepository(HibernateTitleRepository titleRepository) { + this.titleRepository = titleRepository; } - public void testLoadTitle() throws Exception { - Title title = this.titleDao.loadTitle(new Long(10)); + @Test + public void loadTitle() { + Title title = titleRepository.loadTitle(new Long(10)); assertNotNull(title); } } The preceding code listings use the same XML context file referenced by the @ContextConfiguration - annotation (that is, daos.xml), which looks like + annotation (that is, repository-config.xml), which looks like this: <?xml version="1.0" encoding="UTF-8"?> @@ -1283,15 +1293,15 @@ public final class HibernateTitleDaoTests { xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> - <!-- this bean will be injected into the HibernateTitleDaoTests class --> - <bean id="titleDao" class="com.foo.dao.hibernate.HibernateTitleDao"> + <!-- this bean will be injected into the HibernateTitleRepositoryTests class --> + <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> - <!-- dependencies elided for clarity --> + <!-- configuration elided for brevity --> </bean> </beans> @@ -1302,9 +1312,10 @@ public final class HibernateTitleDaoTests { of its setter methods, you might have multiple beans of the affected type defined in your application context: for example, multiple DataSource beans. In such a case, you - can override the setter and use the + can override the setter method and use the @Qualifier annotation to indicate a - specific target bean as follows: + specific target bean as follows, but make sure to delegate to the + overridden method in the superclass as well. // ... @@ -1323,19 +1334,18 @@ public final class HibernateTitleDaoTests { corresponding <bean> definitions. The bean name is used as a fallback qualifier value, so you may effectively also point to a specific bean by name there (as shown above, - assuming that "myDataSource" is the bean id). If there is only one - DataSource bean to begin with, then - the qualifier does not have any effect, independent from the bean - name of that single matching bean. - + assuming that "myDataSource" is the bean id). Alternatively, consider using the @Resource annotation on such - overridden setter methods, defining the target bean name explicitly, - with no type matching semantics. Note that this - always - points to a bean with that specific name, no matter whether there is - one or more beans of the given type. + overridden setter methods. This allows you to specify the name of + the target bean explicitly, but without type matching semantics. + In contrast to the solution above that combined + @Autowired and + @Qualifier, using + @Resource results in the + selection of a bean with that specific name, regardless of + how many beans of the given type exist in the context. // ... @@ -1353,10 +1363,9 @@ public final class HibernateTitleDaoTests { Transaction management In the TestContext framework, transactions are managed by the - TransactionalTestExecutionListener, which is - configured through the - @TestExecutionListeners annotation by - default, even if you do not explicitly declare + TransactionalTestExecutionListener. Note that + TransactionalTestExecutionListener is + configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must provide a PlatformTransactionManager bean in the @@ -1364,9 +1373,9 @@ public final class HibernateTitleDaoTests { @ContextConfiguration semantics. In addition, you must declare @Transactional either at the class or - method level. + method level for your tests. - For class-level transaction configuration (that is, setting the + For class-level transaction configuration (i.e., setting the bean name for the transaction manager and the default rollback flag), see the @TransactionConfiguration entry in the annotation @@ -1392,7 +1401,7 @@ public final class HibernateTitleDaoTests { transactional test method but outside the transactional context, for example, to verify the initial database state prior to execution of your test or to verify expected transactional commit behavior after - test execution (for example, if the test was configured not to roll + test execution (if the test was configured not to roll back the transaction). TransactionalTestExecutionListener supports the @BeforeTransaction and @@ -1405,7 +1414,7 @@ public final class HibernateTitleDaoTests { time. - Any before methods (for example, methods + Any before methods (e.g., methods annotated with JUnit 4's @Before) and any after methods (such as methods annotated with JUnit 4's @After) are executed within a transaction. @@ -1422,9 +1431,7 @@ public final class HibernateTitleDaoTests { integration testing scenario highlighting several transaction-related annotations. Consult the annotation support - section of the reference manual - for - further information and configuration examples. + section for further information and configuration examples. @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -1465,19 +1472,17 @@ public class FictitiousTransactionalTest { Avoid false positives when testing ORM code - When you test code involving an ORM framework such as JPA or - Hibernate, flush the underlying session within - test methods - which update the state of the - session. Failing to flush the ORM framework's underlying session can + When you test application code that manipulates the state of + the Hibernate session, make sure to flush the + underlying session within test methods that execute that code. + Failing to flush the underlying session can produce false positives: your test may pass, but the same code throws an exception in a live, production environment. In the following Hibernate-based example test case, one - method demonstrates a false positive and the other method correctly - exposes the results of flushing the session. + method demonstrates a false positive, and the other method correctly + exposes the results of flushing the session. Note that this + applies to JPA and any other ORM frameworks that maintain an + in-memory unit of work. // ... @@ -1518,20 +1523,16 @@ public void updateWithSessionFlush() { Abstract TestCase that integrates the Spring TestContext Framework with explicit ApplicationContext testing support in a - JUnit 3.8 environment. When you extend the - AbstractJUnit38SpringContextTests class, - you need access to the following protected - instance variables: - + JUnit 3.8 environment. When you extend + AbstractJUnit38SpringContextTests, + you can access the following protected + instance variable: - applicationContext: Perform - explicit bean lookups or test the state of the context as a + applicationContext: + Use this variable to perform explicit bean + lookups or to test the state of the context as a whole. @@ -1545,9 +1546,9 @@ public void updateWithSessionFlush() { Expects a javax.sql.DataSource bean and a PlatformTransactionManager bean to be defined in the ApplicationContext. - When you extend the - AbstractTransactionalJUnit38SpringContextTests - class, you will have access to the following + When you extend + AbstractTransactionalJUnit38SpringContextTests, + you can access the following protected instance variables: @@ -1597,8 +1598,9 @@ public void updateWithSessionFlush() { - applicationContext: Perform - explicit bean lookups or test the state of the context as a + applicationContext: + Use this variable to perform explicit bean + lookups or to test the state of the context as a whole. @@ -1644,7 +1646,7 @@ public void updateWithSessionFlush() { These classes are a convenience for extension. If you do not want your test classes to be tied to a Spring-specific class - hierarchy — for example, if you want to extend directly the class + hierarchy — for example, if you want to directly extend the class you are testing — you can configure your own custom test classes by using @RunWith(SpringJUnit4ClassRunner.class), @@ -1659,7 +1661,7 @@ public void updateWithSessionFlush() { The Spring TestContext Framework offers full integration with JUnit 4.5+ through a custom runner (tested on - JUnit 4.5, 4.6, and 4.7). By annotating test classes with + JUnit 4.5 – 4.8). By annotating test classes with @RunWith(SpringJUnit4ClassRunner.class), developers can implement standard JUnit 4.5+ unit and integration tests and simultaneously reap the benefits of the TestContext @@ -1698,14 +1700,15 @@ public class SimpleTest { TestNG environment. When you extend - AbstractTestNGSpringContextTests you can + AbstractTestNGSpringContextTests, you can access the following protected instance variable: - applicationContext: Perform - explicit bean lookups or test the state of the context as a + applicationContext: + Use this variable to perform explicit bean + lookups or to test the state of the context as a whole. @@ -1720,7 +1723,7 @@ public class SimpleTest { PlatformTransactionManager bean to be defined in the ApplicationContext. When you extend - AbstractTransactionalTestNGSpringContextTests, + AbstractTransactionalTestNGSpringContextTests, you can access the following protected instance variables: @@ -1767,7 +1770,8 @@ public class SimpleTest {
PetClinic example - The PetClinic application, available from the samples repository, + The PetClinic application, available from the + samples repository, illustrates several features of the Spring TestContext Framework in a JUnit 4.5+ environment. Most test functionality is included in the @@ -1821,7 +1825,7 @@ public abstract class AbstractClinicTests extends Abstract The testGetVets() method illustrates how you can use the inherited countRowsInTable() method to easily verify - the number of rows in a given table, thus testing correct behavior + the number of rows in a given table, thus verifying correct behavior of the application code being tested. This allows for stronger tests and lessens dependency on the exact test data. For example, you can add additional rows in the database without breaking tests. @@ -1831,9 +1835,9 @@ public abstract class AbstractClinicTests extends Abstract Like many integration tests that use a database, most of the tests in AbstractClinicTests depend on a minimum amount of data already in the database before the test cases - run. You might, however, choose to populate the database in - your - test cases also — again, within the same transaction. + run. Alternatively, you might choose to populate the database within + the test fixture set up of your test cases — again, + within the same transaction as the tests. @@ -1858,7 +1862,7 @@ public abstract class AbstractClinicTests extends Abstract @ContextConfiguration is declared without any specific resource locations, the Spring TestContext Framework loads an application context from all the beans - defined in AbstractClinicTests-context.xml (that is, + defined in AbstractClinicTests-context.xml (i.e., the inherited locations) and HibernateClinicTests-context.xml, with HibernateClinicTests-context.xml possibly overriding @@ -1869,13 +1873,12 @@ public abstract class AbstractClinicTests extends Abstract public class HibernateClinicTests extends AbstractClinicTests { } - As you can see in the PetClinic application, the Spring - configuration is split across multiple files. As - is typical of large-scale applications, configuration locations are - often specified in a common base class for all application-specific + In a large-scale application, the Spring configuration is + often split across multiple files. Consequently, configuration locations + are typically specified in a common base class for all application-specific integration tests. Such a base class may also add useful instance variables — populated by Dependency Injection, naturally — such as a - HibernateTemplate, in the case of an application + SessionFactory in the case of an application using Hibernate. As far as possible, you should have exactly the same Spring @@ -1884,7 +1887,7 @@ public class HibernateClinicTests extends AbstractClinicTests { } pooling and transaction infrastructure. If you are deploying to a full-blown application server, you will probably use its connection pool (available through JNDI) and JTA implementation. Thus in production you - will use a JndiObjectFactoryBean / + will use a JndiObjectFactoryBean or <jee:jndi-lookup> for the DataSource and JtaTransactionManager. JNDI and JTA will not be @@ -1896,7 +1899,7 @@ public class HibernateClinicTests extends AbstractClinicTests { } choice between application server and a 'local' configuration separated from all other configuration, which will not vary between the test and production environments. In addition, it is advisable to use properties - files for connection settings: see the PetClinic application for an + files for connection settings. See the PetClinic application for an example.
@@ -1909,9 +1912,9 @@ public class HibernateClinicTests extends AbstractClinicTests { } - JUnit: The Spring - Framework's unit and integration test suite, written with JUnit 3.8.2 - and JUnit 4.7 as the testing framework. + JUnit: + A programmer-oriented testing framework for Java. + Used by the Spring Framework in its test suite. @@ -1935,7 +1938,7 @@ public class HibernateClinicTests extends AbstractClinicTests { } EasyMock: Used - extensively by the Spring Framework in its test suite. + by the Spring Framework in its test suite.