Java Unit Testing Niceness

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'.");
    }
}

Leave a Reply

Close Menu