Testing nested structures with AssertJ
Tags: java, assertj, testing
Published: 2019-03-29 20:47:21

---
title: Testing nested structures with AssertJ
published: true
description: 
tags: #java #assertj #testing
---

In a [previous post](https://dev.to/gossie/writing-readable-test-with-assertj-2po8) I described why I like writing tests with [AssertJ](http://joel-costigliola.github.io/assertj/). In this post I would like to describe a nice pattern that helps testing nested structures with [AssertJ](http://joel-costigliola.github.io/assertj/).
Let’s imagin there is a Container class that consists of a name and a collection of Leaf objects.

```java
public class Container {
    
    private String name;
    private List<Leaf> leaves = new ArrayList<>();
    
    public Container(String name, List<Leaf> leaves) {
        this.name = name;
        this.leaves.addAll(leaves);
    }

    public String getName() {
        return name;
    }

    public List<Leaf> getLeaves() {
        return Collections.unmodifiableList(leaves);
    }
}
```

The Leaf class consists of a name and a description.

```java
public class Leaf {
    
    private String name;
    private String description;
    
    public Leaf(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }
    
    public String getDescription() {
        return description;
    }
}
```

For both classes an implementation of AbstractAssert can easily be implemented and could look like that.

```java
public class ContainerAssert extends AbstractAssert<ContainerAssert, Container> {
    
    private ContainerAssert(Container actual) {
        super(actual, ContainerAssert.class);
    }

    public static ContainerAssert assertThat(Container actual) {
        return new ContainerAssert(actual);
    }

    public ContainerAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }
}
```

```java
public class LeafAssert extends AbstractAssert<LeafAssert, Leaf> {
    
    private LeafAssert(Leaf actual) {
        super(actual, LeafAssert.class);
    }

    public static LeafAssert assertThat(Leaf actual) {
        return new LeafAssert(actual);
    }

    public LeafAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }
    
    public LeafAssert hasDescription(String description) {
        Assertions.assertThat(actual.getDescription()).isEqualTo(description);
        return this;
    }
}
```

The simplest way to write a test would look like that.

```java
public class ContainerTest {
    
    @Test
    public void test() throws Exception {
        Container container = classUnderTest.produceContainer();

        assertThat(container).hasName("containerName");
        assertThat(container.getLeaf(0)).hasName("leaf1").hasDescription("description1");
        assertThat(container.getLeaf(1)).hasName("leaf2").hasDescription("description2");
    }
}
```

The thing is, that this approach does not benifit from the fluent interface of [AssertJ](http://joel-costigliola.github.io/assertj/).
The possibility of jumping back and forth between ContainerAssert and LeafAssert would be nice. To achieve this the ContainerAssert is extended by a leaf method, that returns a LeafAssert for a specific Leaf.

```java
public class ContainerAssert extends AbstractAssert<ContainerAssert, Container> {
    
    private ContainerAssert(Container actual) {
        super(actual, ContainerAssert.class);
    }

    public static ContainerAssert assertThat(Container actual) {
        return new ContainerAssert(actual);
    }

    public ContainerAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert leaf(int index) {
        return LeafAssert.assertThat(actual.getLeaves(index), actual);
    }
}
```

The LeafAssert also gets a new method. The parentContainer method jumps back to a ContainerAssert that corresponds to the parent of this Leaf.

```java
public class LeafAssert extends AbstractAssert<LeafAssert, Leaf> {

    private Container currentContainer;
    
    private LeafAssert(Leaf actual, Container currentContainer) {
        super(actual, LeafAssert.class);
        this.currentContainer = currentContainer;
    }

    public static LeafAssert assertThat(Leaf actual) {
        return new LeafAssert(actual, null);
    }
    
    public static LeafAssert assertThat(Leaf actual, Container currentContainer) {
        return new LeafAssert(actual, currentContainer);
    }

    public LeafAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert hasDescription(String description) {
        Assertions.assertThat(actual.getDescription()).isEqualTo(description);
        return this;
    }
    
    public ContainerAssert parentContainer() {
        return ContainerAssert.assertThat(currentContainer);
    }
}
```

The resulting test would look like this.

```java
public class ContainerTest {
  
    @Test
    public void test() throws Exception {
        Container container = classUnderTest.produceContainer();

        assertThat(container)
            .hasName("containerName")
            .leaf(0)
            .hasName("leaf1")
            .hasDescription("description1")
            .parentContainer()
            .leaf(1)
            .hasName("leaf2")
            .hasDescription("description2");
    }
}
```

The problem I see with this approach is, that the LeafAssert class has to handle the currentContainer as internal state. In addition, it is not easy to see which assertion is executed on which instance.

In my opinion this problem can very elegantly be solved by passing a [Consumer](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html) to the ContainerAssert’s leaf method. The [Consumer](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html) accepts a LeafAssert that is initialized with the requested Leaf.

```java
public class ContainerAssert extends AbstractAssert<ContainerAssert, Container> {
    
    private ContainerAssert(Container actual) {
        super(actual, ContainerAssert.class);
    }

    public static ContainerAssert assertThat(Container actual) {
        return new ContainerAssert(actual);
    }

    public ContainerAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public ContainerAssert leaf(int index, Consumer<LeafAssert> consumer) {
        consumer.accept(LeafAssert.assertThat(actual.getLeaves(index)));
        return this;
    }
}
```

That means the LeafAssert class does not need any additional methods.

```java
public class LeafAssert extends AbstractAssert<LeafAssert, Leaf> {
    
    private LeafAssert(Leaf actual) {
        super(actual, LeafAssert.class);
    }

    public static LeafAssert assertThat(Leaf actual) {
        return new LeafAssert(actual);
    }

    public LeafAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert hasDescription(String description) {
        Assertions.assertThat(actual.getDescription()).isEqualTo(description);
        return this;
    }
}
```

In the test class the assertions for a specific Leaf can be passed as a lambda expression to the lead method.

```java
public class ContainerTest {
  
    @Test
    public void test() throws Exception {
        Container container = classUnderTest.produceContainer();

        assertThat(container)
            .hasName("containerName")
            .leaf(0, leaf -> leaf
                .hasName("leaf1")
                .hasDescription("description1"))
            .leaf(1, leaf -> leaf
                .hasName("leaf2")
                .hasDescription("description2")));
    }
}
```

That way no additional state handling is necessary in the ContainerAssert and LeafAssert classes. The indentation makes it easy to see on what level of the nested structure the assertions are executed on.