With this blog I just want to share some tips on how to use two interesting test frameworks that are not very well known, Komarro and Catch Exception.
Class Under Test
The following class is a (modified) example of an anti-corruption layer used in a project I’m working on. It uses an instance of MappingRepository
that is a part of another bounded context to obfuscate EntityId's
(which in the real project is called something else).
@Service
public class EntityIdObfuscationService {
@Inject
private MappingRepository mappingRepository;
/**
* Obfuscate the entity id.
*
* @param entityId The entity id to obfuscate
* @return An obfuscated representation of the the entity id.
* @throws IllegalArgumentException if it's not possible to obfuscate
*/
public ObfuscatedEntityId obfuscateEntityId(EntityId entityId) {
Mapping mapping = mappingRepository.findMapping(entityId.asString());
if (mapping == null) {
throw new IllegalArgumentException("Failed to obfuscate entity id '" + entityId + "'.");
}
String obfuscatedMappingId = mapping.getObfuscatedMappingId();
return new ObfuscatedEntityId(obfuscatedMappingId);
}
}
Happy Flow Test
So let’s see how simple it is to use Komarro
to test the happy flow of this simple class. First off all let’s create a JUnit before
method that initializes the EntityIdObfuscationService
instance that we’ll use in the test:
@Before public void
given_entity_id_obfuscation_service_is_initialized() {
tested = instanceForTesting(EntityIdObfuscationService.class);
}
The instanceForTesting
method is a part of the Komarro
API and is used to create an instance of the class under test and create and inject mocks automatically into the created instance. This means that it’ll create a mock for the MappingRepository
automatically since it’s annotated with Inject
in the EntityIdObfuscationService
instance. We also create an instance of Mapping
that we’ll used in the test:
@Before public void
given_mapping_is_initialized() {
mapping = new MappingImpl("some_mapping");
}
Now that we’ve prepared the class under test let’s take a look at the actual test:
@Test public void
obfuscates_entity_id_when_mapping_for_the_given_entity_id_is_found() {
// Given
given(Mapping.class).isRequested().thenReturn(mapping);
// When
ObfuscatedEntityId obfuscatedId = tested.obfuscateEntityId("someEntityId");
// Then
assertThat(obfuscatedId).isEqualTo(new ObfuscatedEntityId(mapping.getObfuscatedMappingId()));
}
Instead of setting up expectations for a collaborator Komarro
allows us to setup expectations for what’s returned by the collaborator. In this case the collaborator is the mappingRepository
which returns an instance of Mapping
. This means that the given
statement tells Komarro that for any collaborator that happen to return a Mapping
class it should return the mapping
instance we created in the given_mapping_is_initialized
method. This is pretty cool since now we could actually use a completely different collaborator with a different method name and the test would still pass without any change as long as the new collaborator method returns a Mapping
instance.
Exception Flow
There are several ways to test for expected exceptions in a test. You could wrap the tested method in a try-catch statement, add an expected
statement in the @Test
annotation or use the ExpectedException JUnit rule. None of these approaches are perfect though. Using “try-catch” is verbose and doesn’t follow the BDD given-when-then paradigm. Using @Test(expected = IllegalArgumentException.class)
doesn’t allow you to further inspect the exception such as validating the error message. Neither does it follow the given-when-then paradigm. Using the ExpectedException
rule makes the test look a little awkward since you have to define your expected exception before the exception has happened and this too “violates” the given-when-then paradigm. But here the Catch Exception framework can help. Let’s look at the test:
@Test public void
throws_iae_when_mapping_when_the_given_entity_id_is_not_found() {
// Given
given(Mapping.class).isRequested().thenReturn(null);
// When
when(tested).obfuscateEntityId("someEntityId");
// Then
then(caughtException()).isEqualTo(IllegalArgumentException.class).hasMessage("Failed to obfuscate entity id 'someEntityId'.");
}
The catch exception
framework provides us with the when
and then
methods that we’ve statically imported in this example. What happens is that we wrap the instance that that contain the method we expect to throw an exception in the when
method. The framework will return a proxy of the instance and we invoke the method that we expect to throw the exception. Catch Exception
will now catch the exception and store it away for later retrieval. We retrieve the caught exception by calling the then(caughtException())
method. It’ll return a small assertion API similar to fest-assert that we can use to verify the caught exception. The test now reads really nice and follows the given-when-then paradigm.
Conclusion
Komarro and Catch Exception are nice contributions to everyones unit testing tool-suite in Java. There are cases when Komarro doesn’t work well, for example if you have two collaborators that returns different instances of the same type. Also catch exception won’t work well for static methods. But regardless they are worth looking into!
Here’s the full code including imports:
import com.x.y.bc1.Mapping;
import com.x.y.bc1.internal.MappingImpl;
import org.junit.Before;
import org.junit.Test;
import static com.googlecode.catchexception.CatchException.caughtException;
import static com.googlecode.catchexception.apis.CatchExceptionBdd.then;
import static com.googlecode.catchexception.apis.CatchExceptionBdd.when;
import static com.googlecode.komarro.Komarro.given;
import static com.googlecode.komarro.Komarro.instanceForTesting;
import static org.fest.assertions.Assertions.assertThat;
public class EntityIdObfuscationServiceTest {
private EntityIdObfuscationService tested;
private Mapping mapping;
@Before public void
given_entity_id_obfuscation_service_is_initialized() {
tested = instanceForTesting(EntityIdObfuscationService.class);
}
@Before public void
given_mapping_is_initialized() {
userIdMapping = new MappingImpl("some_mapping");
}
@Test public void
obfuscates_entity_id_when_mapping_for_the_given_entity_id_is_found() {
// Given
given(Mapping.class).isRequested().thenReturn(mapping);
// When
ObfuscatedEntityId obfuscatedId = tested.obfuscateEntityId("someEntityId");
// Then
assertThat(obfuscatedId).isEqualTo(new ObfuscatedEntityId(mapping.getObfuscatedMappingId()));
}
@Test public void
throws_iae_when_mapping_when_the_given_entity_id_is_not_found() {
// Given
given(Mapping.class).isRequested().thenReturn(null);
// When
when(tested).obfuscateEntityId("someEntityId");
// Then
then(caughtException()).isEqualTo(IllegalArgumentException.class).hasMessage("Failed to obfuscate entity id 'someEntityId'.");
}
}