miércoles, marzo 02, 2011

Lluita Pels Teus Somnis T´Estan Esperant Fes Que Siguin Certs Abraça´ls.


Testing asynchronous systems are difficult, especially because your tests can fail because of true invalid assertion, but also because your asynchronous system has not had time to process the request and assertion fails. This scenario is typical in JMS environments. With Unit Testing you write a mock that "simulates" JMS behavior, but in case of integration tests, you use a real JMS server for validating all scenario and then some help for dealing with time problem would be welcomed.

So in summary, our test can pass, fail, or failed because tests require more time for processing the request. Let's see an example:

Imagine that we have the next requirement: "when a new user is registered into application, user information should be sent to a JMS Queue".

Moreover when JMS Consumer consumes the user information, it should insert it into database.

Let's write integration tests for these requirements.

@Test
public void addNewUserIsSentToQueue() {
   //Publish an asynchronous event to a JMS system.
   publish(new AddNewUserEvent(user));
   //Retrieve User from database
   User repoUser = userRepository.getUser(user);
   assertThat(repoUser, is(user));
}

Previous test could fail not because of bad code (bug), but because takes more time of publishing and inserting user into repository, than querying to repository and executing assertion.

A possible solution could be:

@Test
public void addNewUserIsSentToQueue() {

   //Publish an asynchronous event to a JMS system.
   publish(new AddNewUserEvent(user));

   try {
      Thread.sleep(5000);
   }catch(Exception e) {
fail(e);
   }

   //Retrieve User from database
   User repoUser = userRepository.getUser(user);
   assertThat(repoUser, is(user));
}

This solution is about wating 5 seconds to give time to consumer for inserting data. It is a possible solution, but I find it hard to read, not a clean solution. In my opinion code should be "human readable", even tests (think about why Hamcrest is important).

Awaitility allows you to express expectations of an asynchronous system in a concise and easy to read manner, avoiding you from dealing with handling threads, timeouts and concurrency issues.

Let's examine some examples:

@Test
public void addNewUserIsSentToQueue() {
//Publish an asynchronous event to a JMS system.
publish(new AddNewUserEvent(user));
//Awaitility waits until asynchronous operation completes
await("user inserted").atMost(5, SECONDS).until(newUserIsAdded());
assertThat(repoUser, is(user));
}

See that unit test is executing the same, but you agree that with Awaitility is cleaner, you can read without any doubt that at most it will wait 5 seconds until new user is added, expired that time, a timeout exception is thrown. But, what does newUserIsAdded() method? It is a simply callback.

private Callable<Boolean> newUserIsAdded() {
return new Callable<Boolean>() {
public Boolean call() throws Exception {
return user.equals(userRepository.getUser(user));
}
}
}

Internally Awaitility has a polling time (100ms by default), meaning that every 100ms the callback is called, if finally returns true, polling is stopped, if not, the exception is thrown.

Depending on polling intervals and callback logic, you can saturate your testing machine, for this reason, you can change that value:

with().pollInterval(1, SECONDS).await("user inserted").atMost(5, SECONDS).until(newUserIsAdded());

Awaitility also supports a waiting depending on a class attribute instead of a method call. It is like watching a flag until it changes its value. I prefer callback approach, in front of monitoring a private attribute, because of breaking encapsulation, but I will explain how to do it because it is another possibility.

await().until( fieldIn(user).ofType(int.class).andWithName("userId"), equalTo(2) );

And that's Awaitility, as I have already mentioned, Awaitility gives you the possibility of expressing expectations in asynchronous integrations tests.

0 comentarios: