Automated testing, and unit testing in particular, is now universally regarded as a best practice for software development. A number of testing frameworks are available, and there remains plenty of room to debate the merits of unit testing versus integration testing, whether to mock objects with interesting behavior with frameworks like jMock or EasyMock or take a more classic approach with basic stubs, when to apply test-driven development (TDD), whether behavior-driven development (BDD) will become commonplace, and so on.
Throughout this book, we've highlighted several best practices, including layering your application and coding to interfaces. In this chapter, we'll demonstrate how these principles lend themselves to building solid test coverage with proper emphasis on exercising aspects of an application in isolation.
A code base that is broken down into layers so that each layer has a unique responsibility is much more testable than code that attempts to combine multiple aspects of functionality into a single class. Testable code is code that is decoupled and divided into logical layers, and well-layered code is testable because it produces small, defined parts of an application's overall vision. By coding to interfaces and leveraging Spring's dependency-injection capabilities, you gain the ability to mock or stub one layer (such as the DAO layer) when you're testing the layer above it (in this case, the service layer that uses DAOs).
Dependency-injection frameworks like Spring are tremendously useful for testing because they make it relatively easy to instantiate classes directly, providing collaborators through code. With Spring in particular, you can automatically inject dependencies within your test classes simply by specifying the appropriate annotations. This allows you to construct an application context for your tests that uses configuration options that may differ quite a bit from your production setup. This flexibility enables you to test your code against a large number of potential inputs.
When it comes to verifying assertions of a persistence tier, it is important to verify the behavior of your DAO and service classes, the configuration details and behavior of your domain classes, and even the collaboration and wiring of dependencies.
We will skim the surface of these strategies in this chapter, but it is important to keep in mind that an effective testing strategy should incorporate both unit and integration tests. Luckily, Spring helps to simplify the creation of both of these kinds of tests, as well as other forms of automated testing, such as functional testing with tools like Selenium.
Spring makes it easy to test specific parts of your code without relying on an application server or other infrastructural details. You can switch between different database implementations and datasources, or test your DAO classes in isolation by mocking these details.
Unit testing is an effective strategy for verifying that a particular class works properly in isolation. Assessing classes in isolation is very valuable, and there is no commensurate replacement for a good unit test. Writing an effective unit test involves the definition of assertions regarding the behavior of specific areas of a class in isolation. Good test coverage is related to which lines of code have their expected behavior verified.
Unlike unit testing, integration testing typically verifies multiple components simultaneously, often by running the same implementation layers used within the production version of an application. For instance, a common practice for integration testing is to instantiate the Spring ApplicationContext
and test a DAO implementation using a real database along with the Spring Hibernate abstractions. The advantage of this approach is that you are touching multiple components, ensuring that all the pieces are working together properly. The disadvantage is that it doesn't provide much granularity to ascertain whether a particular component works properly on its own. For a comprehensive testing strategy, we recommend including both integration and unit tests.
A test suite is a set of individual test classes that are designed to run together and that typically make assertions related to a particular layer or component. For example, you can create a DAO test suite composed of all of your DAO tests. The following example shows all you need to do to create a suite of tests:
public void static testSuite() { return new TestSuite(ArtworkDao.class, CategoryDao.class, ExhibitionDao.class, PersonDao.class); }
Modern IDEs (Eclipse, IntelliJ IDEA, NetBeans, and many more) and other runtime environments (such as Ant and Maven) know how to run both individual unit tests and test suites, which can include both unit and integration tests. It's common to use the notion of suites to strategically bundle tests together. For example, you might want a test suite of fast unit tests that are run on every commit and a different test suite composed of longer-running integration tests, which are done on some scheduled interval.
Functional tests are another strategy for verifying your application is behaving properly. Functional tests provide the most high-level assessment of a code base, and typically require that an application run within a production environment container—for instance, using a servlet container.
Functional tests in a web application context usually involve a series of HTTP requests and then assertions as to the responses that should be returned. For example, a REST web service might include a battery of functional tests that verify the data that is returned when a chunk of XML is POST
ed to a particular URL. Another type of functional test might verify certain HTML elements within a returned HTTP response, given a particular URL.
The downside of functional tests, especially as they relate to verifying HTML markup, is that they tend to be very brittle—meaning they are likely to break as a result of minor changes to the application. However, functional tests do have their place, and they are often an effective tool to verify basic assumptions regarding an application's behavior.
The two biggest unit testing frameworks in the Java community at present are JUnit and TestNG. For our examples, we will use JUnit. JUnit 4's approach is highly annotation-based. The @Test
annotation is all you need to add to define a test:
package com.prospringhibernate.gallery.test; import org.junit.Test; import org.junit.Assert; public class TrivialJUnitTest { @Test public void testSimpleStuff() { String name = "ProSpringHibernate"; Assert.assertEquals("ProSpringHibernate", name); } }
A couple of additional basic JUnit annotations can help define the life cycle of the test. You can run some code immediately before and after each test method using the @Before
and @After
annotations. Guess which one comes before a test? You can also run code before and after all tests in a particular class using @BeforeClass
and @AfterClass
. (Note that the @BeforeClass
method must be static.) There's also an @Ignore
annotation, which allows you to use a @Test
annotation and not run a particular method.
Of course, the main point of a test is to set up a scenario, and then verify a group of assertions. JUnit provides several built-in assertions, such as verifying that two values should be equal, a returned value is not null, and so on. You'll notice many of these annotations in the following example.
package com.prospringhibernate.gallery.test; import org.junit.Test; import org.junit.Ignore; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; public class SimpleJUnitTest { public static String staticName = null; public String memberName = null; @BeforeClass public static void initializeClass() { staticName = "Rod Johnson"; }
@Before public void initializeTest() { memberName = "Gavin King"; } @Test public void simpleEqualsAssertion() { Assert.assertEquals("Rod Johnson", staticName); } @Test public void simpleBooleanAssertion() { Assert.assertFalse(staticName.equals(memberName)); } @Test @Ignore public void dontTestThis() { // notice that this would fail without @Ignore Assert.assertEquals("Rod", memberName); } }
Now let's move beyond the basics and apply some tests to our art gallery application.
The tests in the previous examples are fairly simple, in that they don't have any dependencies on either the Spring container or implementations of other classes or components. Because our tests are isolated to a specific class, they qualify as unit tests. Most of the time, you'll need to go beyond such basic testing to simulate the effects of two or more classes interacting with each other. Integration tests are one way to achieve this, but that generally entails a fair amount of code and tight coupling of tests. An alternate strategy is to use stubs or mocks.
Stubbing and mocking both attempt to simulate the behavior of a particular component or layer within an application, without relying on an actual, full-featured implementation. This approach helps to focus your testing concerns on the code actually being tested, rather than the details of other layers.
Stubbing usually implies that a particular component is faked, with "canned responses" being returned so that the layer being tested is fooled into believing that it is talking to the actual live implementation. Mocking also attempts to simulate a particular layer, but it does more than just return canned responses. A mocked object can also be used to validate expected behavior relating to the layer it is intended to represent. For example, it is possible to specify that a certain method is called on the mock as well as other details that help to provide valuable assertions about how the code you are testing integrates with the mocked layer.
Spring provides several useful mock layers, which can be used as drop-in replacements for various layers within your application. For example, JNDI, Servlet-API, and Portlet mock layers that simulate behavior and associated expectations for their respective layers. For mocking other components, it is possible to use frameworks like jMock, EasyMock or MockObjects. These frameworks provide an extensible means for defining your own mocks dynamically.
For our examples, we will use the very powerful jMock framework. JMock allows you to define the behavior for a particular class, as well as expectations for how particular methods on the class will be called and what they will return within the context of a unit test. jMock employs a simple DSL that allows you to specify a fairly flexible range of behavior. We'll point out a few of the basic jMock concepts when we look at a unit test with mocks later in this chapter.
Let's look at a unit test that attempts to verify the behavior of our ArtworkService
implementation:
package com.prospringhibernate.gallery.test; import com.prospringhibernate.gallery.domain.Person; import com.prospringhibernate.gallery.exception.AuthenticationException; import com.prospringhibernate.gallery.service.ArtworkFacade; import org.junit.Test; import org.junit.Before; import org.junit.runner.RunWith; import org.jmock.Mockery; import org.jmock.Expectations; import org.jmock.integration.junit4.JMock; import org.jmock.integration.junit4.JUnit4Mockery; @RunWith(JMock.class) public class JMockJUnitTestExample { Mockery context = new JUnit4Mockery(); private Person person; private ArtworkFacade artworkService; @Before public void initializeTest() { person = new Person(); person.setUsername("username"); person.setPassword("goodpassword"); // here we use jMock to create a mock based on our Interface artworkService = context.mock(ArtworkFacade.class); } @Test public void testAuthenticationSuccess() throws AuthenticationException { // define expectations for authenticatePerson method context.checking(new Expectations() {{ allowing(artworkService).authenticatePerson("username", "goodpassword"); will(returnValue(person)); }}); artworkService.authenticatePerson("username", "goodpassword"); }
@Test (expected=AuthenticationException.class) public void testAuthenticationFailure() throws AuthenticationException { // define expectations, assuming a bad // username/password context.checking(new Expectations() {{ allowing(artworkService).authenticatePerson("username", "badpassword"); will(throwException(new AuthenticationException())); }}); artworkService.authenticatePerson("username", "badpassword"); } }
Notice that no external dependencies are required for this unit test. This not only helps to isolate the code we are testing, but also significantly speeds up the test. Recall that our ArtworkService
façade depends on several DAOs, including ArtEntityDao
and PersonDao
, but there's nothing in this code that instantiates those classes before the authenticatePerson
method is executed.
We declare that we are using the jMock framework by supplying the @RunWith(JMock.class)
annotation. Next, we define the context instance variable, instantiating a JUnit4Mockery
instance. We can then use the context instance variable to define the behavior and expectations of our mocked ArtworkFacade
.
We create a mocked instance by calling the mock
method on our context instance variable, passing in the interface for the type we wish to mock:
artworkService = context.mock(ArtworkFacade.class
);
This line sets our artworkService
instance to a mocked implementation of our ArtworkFacade
interface. This takes care of the setup.
Now we need to delineate the behavior and expectations for the mock we just defined. This is accomplished in a few steps. Expectations are specified by calling context.checking()
and passing in an anonymous inner class of type Expectations
. The Expectations
class provides most of jMock's DSL features, allowing us to more easily express the behavior of each method we intend to mock.
Things get interesting within this Expectations
block. We can specify the behavior for each method—even defining different behavior based on different parameters or conditions. There are several options that can be defined in mocking our ArtworkFacade
's behavior. However, we must first set the expectations for how our method will be called. Do we expect it to be called exactly once, more than once, within a range, or a specified number of times? Or do we not care whether our method will be called at all? These details are referred to as the invocation count, and represent the first part of the jMock DSL.
In our example, we use the allowing
invocation rule, which tells jMock that we don't care too much about the invocation count. If we did care, we might have used oneOf
, which implies that we expect the method to be called only once. Or we could have used exactly(5).of()
to require that our method be called precisely five times. Similarly, we might have used atLeast(5).of()
to insist that our method be called at least five times. jMock will automatically fail the test if the expectations you specify are not met within the unit test.
So far, our DSL structure looks like the following:
Invocation-Count(mockInstance).method(expectedParams);
In this structure, we specify the expected parameters our mocked method should receive. You can actually define more than one rule, each specifying a different set of parameters. In fact, you don't even need to specify concrete parameters. You can instead specify matchers, which can be used to define more generalized conditions for a particular behavioral rule. The role of a matcher is to allow for the expression of flexible conditions for the expectations of a mocked method. Matchers must be nested within a with
clause:
atLeast(5).of(artworkService). authenticatePerson(with(any(String.class)), with(any(String.class)));
This rule states that authenticatePerson
must be called at least five times, with two parameters that must be of type String
. In this case, we are using the any
matcher, which allows us to specify the type that the parameter must match. There are several other matchers—such as aNull, aNonNull, not
, and same
—and you can even define your own matchers.
So, we've defined the basic expectation of a mocked method. But how do we express a mock's behavior? As you've probably inferred from our example, jMock allows us to define the behavior for a particular "rule" by specifying a will
call after our expectation. We define two possible outcomes for our authenticatePerson
method. The first expectation defines a "good" username and password. We follow this expectation with the following:
will(returnValue(successfulPerson));
This will ensure that a successfulPerson
instance (defined earlier) will be returned whenever pfisher
and goodPassword
are provided to the authenticatePerson
method in our unit test.
Similarly, we define another expectation that assumes an invalid username and password. For this variant, we specify this will
call:
will(throwException(new AuthenticationException());
This will ensure that an AuthenticationException
is always thrown in our unit test, whenever an invalid username and password is provided to the authenticatePerson
method (as long as the invalid username and password match our very stringent conditions, which require that they be username
and badpassword
).
A few other variations can be used with the will
call, such as returnIterator
and doAll
, to provide more flexibility in defining the behavior for our mocked method given a set of conditions. There are also a few more advanced features of jMock that allow you to constrain a set of calls to a particular sequence, or to work with a state machine and verify certain assumptions about the state as it changes from one call to the next. See the JMock documentation to learn more.
We encourage you to explore mocking in more detail, as it is a powerful tool in the creation of an effective unit-testing strategy. Often, developers focus too much on integration testing, simply because unit testing can seem more complicated when "faking" the behavior of dependencies becomes necessary. Mocking is a pragmatic solution to easily defining behavior and expectations for dependencies, encouraging the development of more unit tests.
We've presented a cursory overview for unit testing a piece of code in isolation. However, an effective testing strategy also needs to take into consideration how a particular component works within the context of a running application. This is where integration testing comes in, and Spring serves to simplify the development of integration tests by helping to bootstrap your test suite, resolving dependencies, and handling persistence details, such as transactions.
The Spring Framework provides a convenient layer of abstraction that drastically simplifies switching between these testing frameworks called the TestContext Framework. This framework, which was added as a part of Spring 3, helps to abstract away any test framework-specific details. By using Spring's TestContext Framework, you no longer need to worry about the specifics of a particular testing framework. This makes it especially easy to jump from one testing strategy to another. But more important, the TestContext Framework serves to simplify testing details, making it easier to not only write effective unit and integration tests, but also to integrate Spring dependencies and make useful assertions related to your persistence tier.
Spring testing includes a combination of XML and annotations to affect the way dependencies are injected within a test class. XML configuration works in a similar fashion to the examples you've seen earlier in this book. When defining integration tests, you can use the same XML code (more or less) that you use in your application. However, it is often desirable to override certain beans, such as your datasource, while maintaining the same wiring and implementation details for your application DAOs and service objects. Such a strategy enables you to verify the behavior of your entire persistence tier, while leveraging a specialized test database.
Quite a few Spring-specific annotations are available to get the test configuration stored in your application and the test XML into a running JUnit test. The @RunWith
annotation allows you to specify the test framework you would like to use. As mentioned earlier, one of the primary benefits of using Spring's TestContext Framework is that it allows you to define a test class without tying your code to a particular test framework. You can specify that a particular class is in fact a test by using the @Test
annotation. Then, to indicate which test framework should be used to run the test, you can use @RunWith
, which allows you to specify which test framework to run. For the examples in this chapter, we're going to stick with JUnit 4. We place the following annotation at the top of our test class:
@RunWith(SpringJUnit4ClassRunner.class)
If we wanted to switch to TestNG, we could do that by simply changing the value of this annotation. Adding the @RunWith
annotation to your unit test class will bring Spring into the picture for running the tests and wiring the necessary dependencies. However, there are several options for how the wiring details can be specified. The strategy you choose depends on the unique needs of your application. For instance, if you are building a simple application with only a single datasource, then you can go with an autowiring-by-type strategy, which will implicitly inject the class that matches the type specified in the setter method on which the annotation is added. However, if your application uses multiple datasources, then an autowiring-by-type approach isn't as trivial. For those scenarios, you should use the @Resource
or @Qualifier
annotations, in order to disambiguate the dependency you would like injected.
Though it is usually preferable to let Spring handle dependency injection via configuration or annotation, it's also possible to make your test class implement ApplicationContextAware
or to extend AbstractJUnit4SpringContextTests
, which gives you direct access to the ApplicationContext
, from which you can do a lookup using the bean name:
context.getBean("datasource");
So now you have a handle on some of the options for injecting the layers on which your test class depends. The question that remains is how to inject. In our gallery application, we have defined Spring beans in an XML file named spring-master.xml
, which in turn imports our spring-persistence.xml
configuration. We can import this configuration by adding the following annotation:
@ContextConfiguration(locations = {"classpath:/META-INF/spring/spring-master.xml"})
The @ContextConfiguration
annotation defines the locations of your configuration files. You can determine, on a test-by-test basis, whether to use the Spring configuration file for your full-blown application or to define a more specific unit-testing configuration that is tailored for the needs of a particular test.
Spring configuration via XML is handy, but now you're probably wondering how you can access some beans that are defined in your configuration or those that were picked up through component scanning. Do you remember the @Autowired
annotation that Spring managed beans can use? You can use it in your test code to tell the Spring JUnit Runner
that you need some Spring beans.
Here's what the PersonDAO
test code looks like when we put all of this together:
package com.prospringhibernate.gallery.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.prospringhibernate.gallery.dao.PersonDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-master.xml"})
public class PersonDaoTest
{
@Autowired
PersonDao personDao;
@Test
public void testPerson() {
// insert test logic here
}
}
Let's explore what's happening here. @RunWith
tells JUnit that the test needs some extra logic in order to be set up properly. That extra logic comes in the form of an instance of a class that implements JUnit's Runner
interface. In our case, we have a Spring Runner
called SpringJUnit4ClassRunner
that knows how to set up our application context and inject our test with all of the plumbing that it needs using the standard Spring dependency-injection annotations, such as @Autowired. SpringJUnit4ClassRunner
also looks for some other annotations, including @ContextConfiguration
and @Transactional
.
As you saw in the example, @ContextConfiguration
tells SpringJUnit4ClassRunner
which configuration files you need to set up your testing environment. Behind the scenes, SpringJUnit4ClassRunner
sets up and manages a Spring application context for your unit tests based on the locations you specified in the @ContextConfiguration
. The TestContext
Framework is responsible for actually performing the @Autowired
injection. The TestContext
Framework also keeps track of the results of the status of the current test, such as which method and class were run and which exception was thrown as part of the test. @RunWith
and @ContextConfiguration
are the essential core components of Spring JUnit4 testing.
The TestContext Framework has some performance optimizations to make sure that the framework will load that configuration only once for all of the tests if you run multiple test classes that use the same application context configuration. There are quite a few additional advanced features relating to the TestContext Framework that are worth exploring if you need more advanced testing.
Now that you know how to write a JUnit test class and configure it with Spring XML, you're ready to do some database testing! The simplest form of database testing can be to just reuse those fancy DAOs that you've been working on. You can also apply the usual Spring @Transactional
annotations, along with another annotation:@TransactionConfiguration
.
@TransactionConfiguration
tells the transactional Spring testing environment information about how to get the transactionManager
and whether you would like to commit or roll back the transaction after each test.
The following test takes all of those elements and puts them to work:
package com.prospringhibernate.gallery.test; import java.util.List; import junit.framework.Assert; import org.junit.After; import org.junit.Test; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; Import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; import com.prospringhibernate.gallery.dao.PersonDao; import com.prospringhibernate.gallery.domain.Person; import com.prospringhibernate.gallery.exception.AuthenticationException; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:/META-INF/spring/spring-master.xml"}) @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional()
public class TestContextJUnitIntegrationTest {
Person person;
PersonDao personDao;
public PersonDao getPersonDao() {
return personDao;
}
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@Before
public void preMethodSetup() {
person = new Person();
person.setFirstName("First");
person.setLastName("Last");
person.setUsername("username");
person.setPassword("goodpassword");
person.setRoleLevel(Person.RoleLevel.ADMIN
.getLevel());
person.setVersion(1);
personDao.save(person);
}
@After
public void postMethodTearDown() {
personDao.remove(Person.class, person.getId());
person = null;
}
@Test
public void testPersonPersisted() {
final List<Person> people = personDao.getAll();
Assert.assertEquals(1, people.size());
}
@Test
public void testAuthenticationSuccess() throws AuthenticationException {
Person p = personDao.authenticatePerson("username", "goodpassword");
Assert.assertNotNull(p);
}
@Test (expected=AuthenticationException.class)
public void testAuthenticationFailure() throws AuthenticationException {
personDao.authenticatePerson("username", "badpassword");
}
}
This example is using our web application's Spring configuration as defined in spring-master.xml
, which means that we're using our H2 database configuration. With the @Before
and @After
annotations, we're ensuring that the state of the Person
class is correct for each test method invocation. Finally, in nice discrete units, we test the behavior of our PersonDao
when making a successful call to getAll() Person
entities, a successful call to authenticate against the database, and lastly, a failed attempt to authenticate against the user data that we have scaffolded in with the preMethodSetup()
method.
Notice also that we have set defaultRollback = true
, which will ensure that this method is automatically rolled back after completion. Automatically rolling back your transactions within an integration test is an effective strategy for ensuring that each test method returns the database to its original, pristine state. When defining an integration test that talks to a database, it is important to reduce the potential for database "side-effects" and to ensure that each test stands alone, without being affected by or relying upon previously run methods.
This chapter introduced some testing strategies for Spring applications. Of course, you can do a lot more to test with both JUnit and Spring, not to mention the really powerful testing constructs that are made possible by the mocking frameworks we mentioned. Also, topics such as performance testing and load testing are extremely relevant to persistence.
For more advanced integration testing with databases, we recommend you check out the DbUnit JUnit extension. DbUnit provides excellent facilities for ensuring that your database is in a known state in between tests, as well as tooling to aid in assembling fixture data that can be used across an entire suite of tests.
3.141.197.251