Showing posts with label junit. Show all posts
Showing posts with label junit. Show all posts

Friday, August 13, 2010

Test drive with Arquillian and CDI (Part 2)

The first part of the Arquillian series was mainly focused on working with an in-memory database, DI (dependency injection) and events from the CDI spec. Now we will take a closer look on how to deal with testing Contextual components. For this purpose we will extend our sample project from the first part by adding a PortfolioController class, a conversation scoped bean for handling processing of user's portfolio management.

@ConversationScoped @Named("portfolioController")
public class PortfolioController implements Serializable {

// ...

Map<Share, Integer> sharesToBuy = new HashMap<Share, Integer>();

@Inject @LoggedIn
User user;

@Inject
private TradeService tradeService;

@Inject
private Conversation conversation;

public void buy(Share share, Integer amount) {
if (conversation.isTransient()) {
conversation.begin();
}
Integer currentAmount = sharesToBuy.get(share);
if (null == currentAmount) {
currentAmount = Integer.valueOf(0);
}

sharesToBuy.put(share, currentAmount + amount);
}

public void confirm() {
for (Map.Entry<Share, Integer> sharesAmount : sharesToBuy.entrySet()) {
tradeService.buy(user, sharesAmount.getKey(), sharesAmount.getValue());
}
conversation.end();
}

public void cancel() {
sharesToBuy.clear();
conversation.end();
}

// ...

}

So, let's try out Arquillian! As we already know from the first part we need to create a deployment package, which then will be deployed by Arquillian on the target container (in our case Glassfish 3.0.1 Embedded).


@Deployment
public static Archive<?> createDeploymentPackage() {
return ShrinkWrap.create("test.jar", JavaArchive.class)
.addPackages(false, Share.class.getPackage(),
ShareEvent.class.getPackage())
.addClasses(TradeTransactionDao.class,
ShareDao.class,
PortfolioController.class)
.addManifestResource(new ByteArrayAsset("<beans />".getBytes()), ArchivePaths.create("beans.xml"))
.addManifestResource("inmemory-test-persistence.xml", ArchivePaths.create("persistence.xml"));
}

Next we can start develop a simple test scenario:


  • given user choose CTP share,

  • when he confirms the order,

  • then his portfolio should be updated.

Which in JUnit realms could be written as follows:


@RunWith(Arquillian.class)
public class PortfolioControllerTest {

// deployment method

@Inject
ShareDao shareDao;

@Inject
PortfolioController portfolioController;

@Test
public void shouldAddCtpShareToUserPortfolio() {
// given
User user = portfolioController.getUser();
Share ctpShare = shareDao.getByKey("CTP");

// when
portfolioController.buy(ctpShare, 1);
portfolioController.confirm();

// then
assertThat(user.getSharesAmount(ctpShare)).isEqualTo(3);
}

}

Looks really simple, doesn't it? Well, it's almost that simple but there are some small details which you need to be aware of.

Producers

CDI provides a feature similar to Seam factories or Guice providers. It's called producer and it allows you to create injectable dependency. This could be especially useful when creation of such an instance requires additional logic, i.e. it needs to be obtained from an external source. A logged in user in a web application is a good example here. Thanks to the CDI @Produces construct we can still have very clean code which just works! All we need to do in order to inject the currently logged in user to our bean is as simple as that:

1. Create a @LoggedIn qualifier which will be used to define that a particular injection is expecting this concrete User bean.


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface LoggedIn {
}

2. Implement the producer method which will instantiate the logged in user in the session scope just after he successfully accesses the application, so it will provide an instance of the User class which is of @LoggedIn "type".


@Produces @SessionScoped @LoggedIn User loggedInUser() {
// code for retrieving current user from session
}

3. Decorate all injection points in other beans where we need this instance.


@Inject @LoggedIn
User user;

However this construct could be problematic when writing tests and an attentive reader would probably already be concerned about it. But with Arquillian we will run our test code in the CDI container and there is no need to simulate login procedure, using mock http sessions or any other constructs. We can take full advantage of this fact and create producer method which will replace our original one and provide the user directly from entity manager for example.


@Produces @LoggedIn User loggedInUser() {
return entityManager.find(User.class, 1L);
}

Note: I removed @SessionScoped annotation from loggedInUser() producer method intentionally. Otherwise you could have troubles with Weld proxies and EclipseLink while trying to persist the entity class. For tests it actually does not make any difference.

Context handling

One small problem arrived when I tried to test logic based on the conversation context. I had to figure out a way to programmatically create the appropriate context which then will be used by the SUT (or CUT if you prefer this abbreviation), because I was getting org.jboss.weld.context.ContextNotActiveException. Unfortunately I wasn't able to find anything related to it on the Arquillian forum or wiki, so I desperately jumped to the Seam 3 examples. I read somewhere that they are also using this library to test their modules and sample projects. Bingo! I found what I was looking for. To make the test code more elegant I built my solution the same way as for handling the database in the first part - by using annotations and JUnit rules. Using a @RequiredScope annotation on the test method will instruct JUnit rule to handle proper context initialization and cleanup after finishing the test. To make the code even cleaner we can implement such logic in a dedicated class and treat the enum as a factory:


public enum ScopeType {

CONVERSATION {
@Override
public ScopeHandler getHandler() {
return new ConversationScopeHandler();
}
}

// ... other scopes

public abstract ScopeHandler getHandler();

}

public class ConversationScopeHandler implements ScopeHandler {

@Override
public void initializeContext() {
ConversationContext conversationContext = Container.instance().services().get(ContextLifecycle.class).getConversationContext();
conversationContext.setBeanStore(new HashMapBeanStore());
conversationContext.setActive(true);
}

@Override
public void cleanupContext() {
ConversationContext conversationContext = Container.instance().services().get(ContextLifecycle.class).getConversationContext();
if (conversationContext.isActive()) {
conversationContext.setActive(false);
conversationContext.cleanup();
}
}
}

The JUnit rule will only extract the annotation's value of the test method and delegate context handling to the proper implementation:


public class ScopeHandlingRule extends TestWatchman {

private ScopeHandler handler;

@Override
public void starting(FrameworkMethod method) {
RequiredScope rc = method.getAnnotation(RequiredScope.class);
if (null == rc) {
return;
}
ScopeType scopeType = rc.value();
handler = scopeType.getHandler();
handler.initializeContext();
}

@Override
public void finished(FrameworkMethod method) {
if (null != handler) {
handler.cleanupContext();
}
}
}

Finally here's fully working test class with two additional test scenarios. I also used DBUnit add-on from first post for convenience.


@RunWith(Arquillian.class)
public class PortfolioControllerTest {

@Rule
public DataHandlingRule dataHandlingRule = new DataHandlingRule();

@Rule
public ScopeHandlingRule scopeHandlingRule = new ScopeHandlingRule();

@Deployment
public static Archive<?> createDeploymentPackage() {
return ShrinkWrap.create("test.jar", JavaArchive.class)
.addPackages(false, Share.class.getPackage(),
ShareEvent.class.getPackage())
.addClasses(TradeTransactionDao.class,
ShareDao.class,
TradeService.class,
PortfolioController.class)
.addManifestResource(new ByteArrayAsset("<beans />".getBytes()), ArchivePaths.create("beans.xml"))
.addManifestResource("inmemory-test-persistence.xml", ArchivePaths.create("persistence.xml"));
}

@PersistenceContext
EntityManager entityManager;

@Inject
ShareDao shareDao;

@Inject
TradeTransactionDao tradeTransactionDao;

@Inject
PortfolioController portfolioController;

@Test
@PrepareData("datasets/shares.xml")
@RequiredScope(ScopeType.CONVERSATION)
public void shouldAddCtpShareToUserPortfolio() {
// given
User user = portfolioController.getUser();
Share ctpShare = shareDao.getByKey("CTP");

// when
portfolioController.buy(ctpShare, 1);
portfolioController.confirm();

// then
assertThat(user.getSharesAmount(ctpShare)).isEqualTo(3);
}

@Test
@PrepareData("datasets/shares.xml")
@RequiredScope(ScopeType.CONVERSATION)
public void shouldNotModifyUserPortfolioWhenCancelProcess() {
// given
User user = portfolioController.getUser();
Share ctpShare = shareDao.getByKey("CTP");

// when
portfolioController.buy(ctpShare, 1);
portfolioController.cancel();

// then
assertThat(user.getSharesAmount(ctpShare)).isEqualTo(2);
}

@Test
@RequiredScope(ScopeType.CONVERSATION)
@PrepareData("datasets/shares.xml")
public void shouldRecordTransactionWhenUserBuysAShare() {
// given
User user = portfolioController.getUser();
Share ctpShare = shareDao.getByKey("CTP");

// when
portfolioController.buy(ctpShare, 1);
portfolioController.confirm();

// then
List<TradeTransaction> transactions = tradeTransactionDao.getTransactions(user);
assertThat(transactions).hasSize(1);
}

@Produces @LoggedIn User loggedInUser() {
return entityManager.find(User.class, 1L);
}

}

For the full source code you can jump directly to our google code repository.

Conclusion

As you can see playing with Arquillian is pure fun for me. Latest 1.0.0.Alpha3 release brought a lot of new goodies to the table. I hope that the examples in this blog post convinced you that working with different scopes is quite straightforward and requires just a little bit of additional code. However it's still not the ideal solution because it's using Weld's internal API to create and manage scopes. So if you are using a different CDI container you need to figure out how to achieve it, but it's just a matter of adjusting ScopeHandler implementation to your needs.

There is much more to write about Arquillian so keep an eye on our blog and share your thoughts and suggestions through comments.

Tuesday, July 13, 2010

Test drive with Arquillian and CDI (Part 1)

Here at Cambridge Technology Partners we are as serious about testing as we are about cutting-edge technologies like CDI. Last time we wrote about testing EJBs on embedded Glassfish and now we are back with something even more powerful, so keep on reading!

Background

Recently I was involved in a project based on JBoss Seam where we used Unitils for testing business logic and JPA. I really like this library, mainly because of the following aspects:

  • Provides easy configuration and seamless integration of JPA (also a little bit of DI).
  • Greatly simplifies management of the test data. All you need to do in order to seed your database with prepared data is providing a xml dataset (in a DBUnit flat xml format) and then add @DataSet annotation on the test class or method.
Unitils library is definitely an interesting topic for another blog entry, but since we are going to dive into the Java EE 6 testing those of you who are not patient enough for next blog entry can jump directly to the tutorial site. I'm sure you will like it.

The only thing in Unitils which I'm not really comfortable with, is the fact that this library is not really designed for integration testing. The example which is clearly demonstrating it is an observer for Seam events. In this particular case we might need to leave unit testing world (mocks, spies and other test doubles) and develop real integration tests. The SeamTest module together with JBoss Embedded could help but it's really a tough task to make it running with Maven. On the other hand JBoss AS wasn't the target environment for us. Thankfully there is a new kid on the block from JBoss called Arquillian. In the next part of this post I will try to summarize my hands-on experience with this very promissing integration testing library. But first things first, let's look briefly at CDI events.

CDI Events

We are going to have JEE6 workshops for our customers and I was extremely happy when my colleagues asked me to play around with Arquillian and prepare some integration tests. I picked up a piece of logic responsible for logging market transactions based on CDI events. In brief it is a design technique which provides components interaction without any compilation-time dependencies. It's similar to the observer pattern but in case of CDI events, producers and observers are entirely decoupled from each other. If following example won't give you a clear explanation of the concept please refer to this well written blog post. Let's take a look at quite simplified code example.

import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;

@Stateless
public class TradeService {

@Inject @Buy
private Event<ShareEvent> buyEvent;

public void buy(User user, Share share, Integer amount) {
user.addShares(share, amount);
ShareEvent shareEvent = new ShareEvent(share, user, amount);
buyEvent.fire(shareEvent);
}

...

}

import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class TradeTransactionObserver implements Serializable {

...

@Inject
TradeTransactionDao tradeTransactionDao;

public void shareBought(@Observes @Buy ShareEvent event) {
TradeTransaction tradeTransaction = new TradeTransaction(event.getUser(), event.getShare(), event.getAmount(), TransactionType.BUY);
tradeTransactionDao.save(tradeTransaction);
}

...

}

To preserve the clear picture I'm not going to include Share, TradeTransaction, User and ShareEvent classes. What is worth to mention however is that the instance of ShareEvent contains user, a share which he bought and amount. In the User entity we store a map of shares together with amount using new @ElementCollection annotation introduced in JPA 2.0. It allows to use entity classes as keys in the map.

@ElementCollection
@CollectionTable(name="USER_SHARES")
@Column(name="AMOUNT")
@MapKeyJoinColumn(name="SHARE_ID")
private Map<Share, Integer> shares = new HashMap<Share, Integer>();

Then in the TradeTransaction entity we simply store this information and additionally date and TransactionType. Complete code example could be downloaded from our google code page - see Resources section at the bottom of the post.

The very first test

We will use a following scenario for our test example (written in the BDD manner):

  • given user choose a CTP share,
  • when he buys it,
  • then market transaction should be logged.
So the test method could look as follows:

@Test
public void shouldLogTradeTransactionAfterBuyingShare() {
// given
User user = em.find(User.class, 1L);
Share share = shareDao.getByKey("CTP");
int amount = 1;

// when
tradeService.buy(user, share, amount);

// then
List<TradeTransaction> transactions = tradeTransactionDao.getTransactions(user);
assertThat(transactions).hasSize(1);
}

In the ideal world we could simply run this test in our favourite IDE or a build tool without writing a lot of plumbing code or dirty hacks to set up the environment like Glassfish, JBoss AS or Tomcat. And here's when Arquillian comes to play. The main goal of this project is to provide a convenient way for developers to run tests either in embedded or remote containers. It's still in alpha version but amount of already supported containers is really impressive. It opens the door to world of easy and pleasant to write integration tests. There are only two things required in order to make our tests "arquillian infected":

  1. Set @RunWith(Arquillian.class) annotation for you test class (or extend Arquillian base class if you are a TestNG guy).
  2. Prepare a deployment package using ShrinkWrap API in a method marked by the @Deployment annotation.

@Deployment
public static Archive<?> createDeploymentPackage() {
return ShrinkWrap.create("test.jar", JavaArchive.class)
.addPackages(false, Share.class.getPackage(),
ShareEvent.class.getPackage(),
TradeTransactionDao.class.getPackage())
.addClass(TradeService.class)
.addManifestResource(new ByteArrayAsset("<beans/>".getBytes()), ArchivePaths.create("beans.xml"))
.addManifestResource("inmemory-test-persistence.xml", ArchivePaths.create("persistence.xml"));
}

Odds and ends

Until now I guess everything was rather easy to grasp. Unfortunately while playing with tests I encountered a few shortcomings but found the solutions which I hope make this post valuable for readers. Otherwise you could simply jump to the user guide and code examples, don't you?

JAR hell

The most time consuming issue were the dependencies conflicts better known as JAR hell. The target environment for the workshop application is Glassfish v3 so I used the embedded version for my integration tests. I decided to have my tests as integral part of the project and here problems began.

The main problem is that you cannot use javaee-api because you will get exceptions while bootstrapping the container more or less similar to : java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/validation/constraints/Pattern$Flag (related thread on JBoss forum). I also recommend not to download separated jars for each project which you are using because you will get even more exceptions :)

Important remark here: if you are using JPA 2.0 with Criteria API and a hibernate-jpamodelgen module for generating metamodel classes then you should also exclude org.hibernate.javax.persistence:hibernate-jpa-2.0-api dependency to avoid yet another class conflict.

You have basically two options:

  1. Use glassfish-embedded-all jar since it already contains all needed APIs.
  2. Create a separated project for integration testing and forget about everything what I mentioned in this section.

Preparing a database for testing

Next step is to create a data source for the Glassfish instance. But first we need to tell Arquillian to not delete the Glassfish server folder after each deployment / test execution (which is the default behaviour). All you need to do is to create a arquillian.xml file and add following configuration:

<arquillian xmlns="http://jboss.com/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:glassfish="urn:arq:org.jboss.arquillian.glassfish.embedded30">
<glassfish:container>
<glassfish:bindPort>9090</glassfish:bindPort>
<glassfish:instanceRoot>src/test/glassfish-embedded30</glassfish:instanceRoot>
<glassfish:autoDelete>false</glassfish:autoDelete>
</glassfish:container>
</arquillian>

Then we need to take a domain.xml file from our normal Glassfish instance (i.e. from ${glassfish_home}/glassfish/domains/domain1/config), remove all <applications> and <system-applications> nodes, add new data source and copy it to the src/test/glassfish-embedded-30/config folder. We will use HSQL 1.8.0.7 version in our tests (2.0 version is causing some problems with DBUnit).

<domain log-root="${com.sun.aas.instanceRoot}/logs" application-root="${com.sun.aas.instanceRoot}/applications" version="22">
<system-applications />
<applications />
<resources>
...
<jdbc-connection-pool res-type="java.sql.Driver" description="In memory HSQLDB instance" name="arquilliandemo" driver-classname="org.hsqldb.jdbcDriver">
<property name="URL" value="jdbc:hsqldb:mem:arquilliandemomem" />
<property name="user" value="sa" />
<property name="password" value="" />
</jdbc-connection-pool>
<jdbc-resource pool-name="arquilliandemo" jndi-name="arquilliandemo-ds" />
</resources>
<servers>
<server name="server" config-ref="server-config">
...
<resource-ref ref="arquilliandemo-ds" />
</server>
</servers>
<configs>
...
</configs>
</domain>

The last file which you need to take from ${glassfish_home}/glassfish/domains/domain1/config folder is server.policy. And that's it! You have running Glassfish with HSQL database ready for some serious testing.

Data preparation

As I mentioned in the introductory section I really like Unitils and the way how you can seed the database with the test data. The only thing to do is to provide an XML file in flat dbunit format like this one:

<dataset>
<user id="1" firstname="John" lastname="Smith" username="username" password="password" />
<share id="1" key="CTP" price="18.00" />
</dataset>

and then put the @DataSet("test-data.xml") annotation either on the test method or a class.

I was really missing this feature so I decided to implement it myself. Very cool way of adding such behaviour is by using a JUnit rule. This mechanism, similar to interceptors, has been available since 4.7 release. I choose to extend the TestWatchman class since it provides methods to hook around test invocation. You can see the rule's logic based on the DBUnit flat xml data for seeding database in the example project. All you need to do is to create a public field in your test class and decorate it with @Rule annotation. Here's the complete test class.

@RunWith(Arquillian.class)
public class TradeServiceTest {

@Deployment
public static Archive<?> createDeploymentPackage() {
return ShrinkWrap.create("test.jar", JavaArchive.class)
.addPackages(false, Share.class.getPackage(),
ShareEvent.class.getPackage(),
TradeTransactionDao.class.getPackage())
.addClass(TradeService.class)
.addManifestResource(new ByteArrayAsset("<beans />".getBytes()), ArchivePaths.create("beans.xml"))
.addManifestResource("inmemory-test-persistence.xml", ArchivePaths.create("persistence.xml"));
}

@Rule
public DataHandlingRule dataHandlingRule = new DataHandlingRule();

@PersistenceContext
EntityManager em;

@Inject
ShareDao shareDao;

@Inject
TradeTransactionDao tradeTransactionDao;

@Inject
TradeService tradeService;

@Test
@PrepareData("datasets/shares.xml")
public void shouldLogTradeTransactionAfterBuyingShare() {
// given
User user = em.find(User.class, 1L);
Share share = shareDao.getByKey("CTP");
int amount = 1;

// when
tradeService.buy(user, share, amount);

// then
List<TradeTransaction> transactions = tradeTransactionDao.getTransactions(user);
assertThat(transactions).hasSize(1);
}

}

I must admit that it's a JUnit specific solution, but you can always implement your own @BeforeTest and @AfterTest methods to achieve the same result in TestNG.

DBUnit gotchas

Using DBUnit's CLEAN_INSERT strategy (or deleting table content after test execution by using DELETE_ALL) could raise constraint violation exceptions. HSQL provides special SQL statement for this purpose and the sample project is invoking this statement just before DBUnit.

Final thoughts

All in all Arquillian is a really great integration testing tool with full of potential. It's just great that the JBoss guys are aiming to provide support for almost all widely used application servers and web containers. As you could see from the examples above it's not that hard to have tests for more sophisticated scenarios than you can find in the user guide. Keep your eyes on Arquillian - the roadmap is really promising.
In the upcoming second part I will dive into CDI contexts and demonstrate how to use Arquillian for testing contextual components.
If you are writing an application for the Java EE 6 stack while not using Arquillian is a serious mistake!

Resources