Go forth and test in-container!
Arquillian: A Component Model for Integration Testing - Part 2
Don't Mock, Integrate
In a paper titled "What Is Software Testing? And Why Is It So Hard?" James Whittaker offers a well-defined classification of the various levels of testing (i.e., unit, integration, system, functional, etc). However, he struggles with the popular question of when and how to draw the line between unit and integration testing:
Unit testing sometimes requires the construction of throwaway driver code and stubs and is often performed in a debugger.
Some components can't be unit tested because they weren't designed with testing in mind, meaning they have lots of dependencies that are hard to mock. Even if they can be mocked, how much effort should you put into creating mocks? And how many mocks are permitted before a test loses its value?
I believe that anything more than a couple of lines of throwaway driver code and stubs is time thrown away. Mocks are not a substitute for integration testing.
We also shouldn't be satisfied with only being able to use a debugger during unit testing. Debuggers are crucial for productivity. Why can't we use them for integration testing as well? Or why is it so uncommon? Are integration tests that esoteric?
That brings us back to the lack of a programming model for writing and executing integration tests (and on up, including system and functional tests).We don't want to beat around the bush by using mocks. We just want integration tests to be:
• as easy as writing unit tests
• executed in the IDE (incremental builds, debugging, etc)
• ramped up gradually (instead of "big bang" integration)
By providing a component model for tests, Arquillian eliminates the testing bandgap and flattens the configuration complexity curve as it graduates from unit to integration tests and beyond. The result is the testing continuum shown in Figure ALL-2. With Arquillian in your toolbox, you'll be able to move comfortably between unit and integration testing.
Now that you've had your day's worth of theory, let's step through an example to see where a component model fits with testing and learn how Arquillian is able to leverage it to maintain this testing continuum.
Graduating from Unit to Integration Testing and Beyond
We'll work from a unit test to an integration test to give you a true appreciation for just how smooth Arquillian makes this transition. When you put them side-by-side, the Arquillian test is going to look very similar to the unit test, and that's the goal.
1 Arquillian supports both JUnit 4 and TestNG 5 in nearly the same way. To make the example easy to follow, we’ll just select JUnit 4.
A Unit to Test
To keep it simple, we'll make our unit to test a calculator class. Basic addition isn't very interesting as a service, though, so let's make it a calculator that computes the monthly payment amount for a fixed term mortgage. The example class is shown in Listing ALL-1. Currency calculations can get a little dicey in Java if you don't take into account precision, scale and rounding. So it's worthy of testing. Let's write a JUnit 4-based unit test case (Listing ALL-2) that ensures our calculator isn't going to be the source of any bank errors.
The test case in Listing ALL-2 can be executed using any JUnit 4 plugin.
Notice that the test case is initiating MortgageCalculator itself. If this were just a class from a library, that would be reasonable. But let's assume that the class functions as an EJB component (i.e., a service). EJB 3.1 makes this shift as simple as adding a single annotation to the class, as we've done by adding the @Stateless annotation in Listing ALL-3.
Don't be turned off by the use of EJB for this example. I've selected EJB because it's a recognizable component model. Rest assured, Arquillian can support any programming model, not just EJB. Our unit test will still work with this annotation present. That's because all the logic is self-contained in the business method and the component doesn't use any container features. But let's put that fact aside for the moment and assume that we do want to test this class acting in its role as a component. We want to test an EJB instance.
The definition James Whittaker offers for software testing in the aforementioned paper applies well here (emphasis mine):
Software testing is the process of executing a software system to determine whether it matches its specification and executes in its intended environment.
Unit testing can help test inputs and outputs, but what it lacks is a mechanism by which we can execute a component in its intended environment. That's the way other components will use it and we want to make sure that part works. We want to bridge theory and practice and test the component for real. We want integration testing.
That brings us to the question, "How do you test an EJB?" We need to start an EJB container and get it to create an instance and hand it to us. That means we need a container running in our test case. Here's where the component model for tests starts to come into play.
You may have read about the embedded EJB container that was introduced in EJB 3.1 primarily for the purpose of testing. That's good news! Using it would certainly satisfy the requirement of getting an EJB container setup in our test. Listing ALL-4 shows an example of how to boot the embedded EJB 3.1 container and use it to lookup an instance of our mortgage calculator component:
At first, the approach in Listing ALL-4 seems pretty reasonable (and a significant improvement over the EJB of old). But there are a number of problems with it:
You need to add before/after logic so the container is only created once per suite because startup isn't instantaneous. You have to look up the component manually, rather than using the more convenient @EJB field/method injection.
The embedded EJB container is going to be scanning all over your project for EJBs – maybe not want you want.
The test is relying on a JNDI name for the component that is specific to the embedded environment.
An embedded environment isn't the real deal.
The test is tied to the embedded EJB container.
Let's step away from the embedded EJB container for a moment and look at a much simpler, declarative style that Arquillian's component model brings to this test.
There's A Component Model in My Test!
Anxious to see what a component model for a test looks like? Direct your attention to Listing ALL-5. You should immediately recognize several differences in the Arquillian version of this test. But, before addressing what's present, let's look at what isn't. Notice there are no setup or tear down methods. We are going to push all the work of starting and stopping the container to Arquillian. And no controller code means a portable test. More on that later. The other three differences in this test are stamped by their corresponding annotations:
3. @EJB (and similar)*
* The @EJB annotation represents the link that Arquillian is providing to the application component model. If the application were employing JSR-299, you could use @Inject to get a reference to the component to test. Arquillian is not tied to EJB.
Let's study how each of these annotations contribute to the test's component model.
The Arquillian Test Runner
Scanning from top to bottom, the first thing you'll notice is that the test class is now annotated with @RunWith(Arquillian.class). This annotation describes its purpose well.
The test class is run with Arquillian. Delegation to the Arquillian runner occurs immediately after the test is launched. As the JavaDoc for @RunWith explains, JUnit will invoke the class [that @RunWith] references to run the tests in [the annotated] class instead of the runner built into JUnit.
The Arquillian class is also a listener that hooks into the JUnit lifecycle to provide in-container testing functionality. It handles three responsibilities:
- Control the lifecycle of the target container (i.e., runtime).
- Orchestrate deployment of test artifacts to the container.
- Execute tests inside the container, giving them access to the deployed components.
Don't worry, the delegation to Arquillian won't change how you run the test. All your standard JUnit plugins will still function. They have no idea any of this extra activity is going on under the covers. As far as they are concerned, this is just a plain old test case.