Mockarro TDD example

Since the day I came up with  Mockarro I find it very hard to evaluate clearly its usefulness. I guess it’s always pretty hard to make a clear judgement on an idea when the border between its advantages and disadvantages is fuzzy. It’s probably even harder if the idea is yours.

Anyhow, I recently decided to bring Mockarro closer to its first release and to expose it to the outer world.

It is important to remember that Mockarro provides a way of defining the indirect inputs to the tested method. It trades the specification of ‘how’ the indirect inputs are obtained for ‘what’ the indirect inputs are.

Let’s start with a simple TDD example. We are going to create a part simple application that searches the database of planets that are possibly inhabitable. The whole application is supposed to be created in a top-down manner. We will focus first on finding a planet whose radius is most similar to the Earth’s mean radius.

	@Test
	public void retrievePlanetWithRadiusMostSimilarToEarthsTest() {
		// given
		Planet earth = planet("earth", 6371);
		given(Planet.class).isRequested().thenReturn(earth);

		given(new TypeLiteral<List<Planet>>() {}).isRequested().thenReturn(
				asList(earth, planet("Mars", 3396), planet("Tatooine", 55000),
						planet("Arrakis", 10123), planet("Solaris", 12700)));

		// when
		Planet planet = planetService
				.retrievePlanetWithRadiusMostSimilarToEarths();

		// then
		assertThat(planet).isNotNull().isNotSameAs(earth);
		assertThat(planet.getName()).isEqualTo("Mars");
	}

The code above defines the expected behavior of the retrievePlanetWithRadiusMostSimilarToEarthsTest method. The fixture set-up section (the given section) defines the indirect inputs to the method under test. As opposed to the standard Mocking frameworks like Mockito or !EasyMock, Mockarro does not couple the test method to collaborators within the given section. It does not define how the indirect inputs will be provided. It does, on the other hand, clearly specify what the indirect inputs the method under test are.
The method that is going to be implemented, briefly, will be given a list of all planets in the database and an instance of the planet Earth. It is required to find a planet whose radius is most similar Earth’s radius, but at the same time it is required not to return the Earth as the result.

A possible implementation of the method could use a PlanetRepository to get both: the instance of a planet Earth and a list of all planets available for the application.

	@Inject
	private PlanetRepository planetRepository;

	public Planet retrievePlanetWithRadiusMostSimilarToEarths() {
		Planet earth = planetRepository.getByName("earth");

		if (earth != null) {
			double earthRadius = earth.getKilometersOfRadius();

			Planet mostSimilarPlanet = null;
			double smallestDiff = Double.POSITIVE_INFINITY;
			for (Planet planet : planetRepository.getAllPlanets()) {
				if (planet != earth) {

					double diff = Math.abs(planet.getKilometersOfRadius()
							- earthRadius);
					if (diff < smallestDiff) {
						smallestDiff = diff;
						mostSimilarPlanet = planet;
					}
				}
			}
			return mostSimilarPlanet;
		} else {
			throw new IllegalStateException(
					"No earth in the planet repository");
		}
	}

Let’s assume that at some stage of the application life cycle a SolarSytemRepository is created. The Earth will be obtained directly from the new service. The input parameters will not change whatsoever.


	@Inject
	private PlanetRepository planetRepository;

	@Inject
	private SolarSystemService solarSystemService;

	public Planet retrievePlanetWithRadiusMostSimilarToEarths() {
		Planet earth = solarSystemService.getEarth();

		if (earth != null) {
			double earthRadius = earth.getKilometersOfRadius();

			Planet mostSimilarPlanet = null;
			double smallestDiff = Double.POSITIVE_INFINITY;
			for (Planet planet : planetRepository.getAllPlanets()) {
				if (planet != earth) {

					double diff = Math.abs(planet.getKilometersOfRadius()
							- earthRadius);
					if (diff < smallestDiff) {
						smallestDiff = diff;
						mostSimilarPlanet = planet;
					}
				}
			}
			return mostSimilarPlanet;
		} else {
			throw new IllegalStateException(
					"No earth in the planet repository");
		}
	}

This refactoring does not require the test method to be changed. The input parameters stayed the same (both direct and indirect). It has to be pointed out though, that the test method will have to be changed every time the set of the indirect input parameters is modified.

The source code of the examples in this post can be found at https://github.com/marekdec/planetary-system. The interesting stages of the development process have been tagged, navigate to https://github.com/marekdec/planetary-system/tags to find them.

Advertisements

, ,

  1. #1 by AnotherBritInDenmark on February 3, 2012 - 10:38 am

    I like the concept, and think this kind of approach removes a lot of boilterplate testing configuration.
    Isnt it nicer for the syntax to look like this though:

    when(planetService).shouldReturnA(Planet.class).thenReturn(earth);
    when(planetService).shouldReturnAListOf(Planet.class).thenReturn(listOfPlanets);

    If you really need to configure the returns on a method level (two different methods return the same class), then you can return to specific method mocking.

  2. #2 by marekdec on February 3, 2012 - 4:49 pm

    Hi AnotherBritInDenmark,

    Indeed, it’s an interesting approach. I’ll definitely have this in mind if Mockarro development goes on.
    Please note, however, that your proposal is not only syntatically but also semantically different from the the original Mockarro’s one. The planetService is not the one returning the Planet.class, it is the actual System Under Test and it is the one that requests the Planet.class from its collaborators (in the example it is the PlanetRepository that returns the Earth, and the PlanetRepository is never mentioned within the test’s code). To maintain the meaning your snippet would have to be rephrased to:

    when(planetService).requests(Planet.class).thenReturn(earth);

    Also, I agree that the TypeLiteral idiom is a bit ugly, and that was not my first choice. See this thread for details http://groups.google.com/group/mockarro/browse_frm/thread/246ed3d76987447a.
    In the first take, the syntax for generic types looked a bit like
    given(List.class).of(String.class).isRequested().thenReturn(aListOfStrings);
    It quickly turned out, that it was not good enough when some more complex types where used. The simpliest way to overcome this was to use the TypeLiteral token. It’s really hard to create a good dsl, and I’m sure it will evolve in the future.

    Regards,
    Marek

  3. #3 by AnotherBritInDenmark on February 6, 2012 - 1:33 pm

    So basically anything marked as @Inject in the “system under test” is valid for having its methods being “mocked” by return value types ?

  4. #4 by marekdec on February 6, 2012 - 3:41 pm

    The short answer is: yes.

    The idea of Mockarro is to provide a way to define the indirect inputs of a method _under_test without the need to specify how those inputs are retrieved (somehow ‘what’ over ‘how’ philosophy). And as in the current implementation it is assumed that the indirect inputs come from the collaborators and the collaborators are marked with the @Inject annotation (but not only, as also constructor and setter injection are supported, @Autowired is supported too, etc), all the collaborators methods are valid for the Mockarro’s style of mocking.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: