Friday, August 13, 2010
Java People Spotlight: Bartek Majsak
So let's see how geeky his answers are then!
Java Competence Role:
Senior Developer [aka Mr. T due to his extreme passion on Testing]
My Master Kung-Fu Skills:
I can mock you out even if you are static ;)
I'd be excited to get my hands dirty on:
Scala and/on Android
Q&A
Q: Hi Bartek, how would your message look like if you would have to tell it via Twitter what you are currently doing?
A: Thinking how to design my own #arquillian TestEnricher and how to complete "The Challenge of Hades" in GoW at the same time.
Q: ... and having some drops of Sudden Death on top of it? ;-)
Q: What was the greatest piece of code you have ever written so far?
A: Please come back to me with this question when I will retire :) After a few days, the code which I thought was the most brilliant I've ever written... I refactor, so... I don't have a good answer for this question yet :D
Q: What is the best quote you have ever heard about programming?
A: "If debugging is the process of removing bugs, then programming must be the process of putting them in." (E.W. Dijkstra)
Q: What is the best quote you have heard from our managers?
A: "There is nothing as permanent as a temporary solution".
Q: What is the most cutting-edge technology or framework you actually used on projects?
A: CDI (JSR-299) and Arquillian.
Q: What is your favorite podcast?
A: I used to listen podcast while I was commuting but nowadays I'm a little bit out of the loop - living too close to the office ;) Of course I like Java Posse (who doesn't?!) and Software Engineering Radio. I'm also addicted to Parleys, DZone and InfoQ.
Q: Which Java book can you recommend and for what reason?
A: I really enjoyed reading "Implementation Patterns" by Kent Beck and "Working Effectively with Legacy Code" by Michael Feathers. The first one gives you interesting hints for how to express yourself through code by keeping it clean and easy to understand for others. The second one will help you to stay sane while digging into the code of a person who definitely never read the first book. If you are serious about testing you should read Growing Object-Oriented Software Guided by Tests by Steve Freeman & Nat Pryce as well as xUnit Test Patterns by Gerard Meszaros. Of course all books recommended already by my colleagues are also just great but I didn't want to repeat them here.
Q: DRY pattern... :-) .... Well thanks for your answers and enjoy your mock-ito this evening at the CTP Summer Event!!
You can further follow Bartek's web presence in these directions:
- linked in: http://ch.linkedin.com/in/bartoszmajsak
- lastfm: http://www.last.fm/user/majson
- twitter: http://twitter.com/majson
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.