How Can Hierarchical Test Structure Absolutely Make a Mess?

Alex Fedorov
Feb 18, 2019 · 4 min read
Image for post
Image for post
source: pexels

Have you ever written your unit tests using a simple xUnit style testing framework?

Then you probably know, as tests get more complex, the more boilerplate and duplication they collect, either spread among the test methods, or setup functions.

Now, hierarchical context frameworks are pretty robust to mitigate this boilerplate problem and remove this duplication. They allow you to have nested contexts each one having their own little bit of setup, and “inheriting” the setup of parent contexts.

This way, you can express lots of different scenarios without actually repeating yourself even once in the test setup code.

Great, isn’t it?

Now, what if I told you that hierarchical test structure can cause more subtle duplication (that it was supposed to prevent in the first place) that is hard to spot and refactor?

Let me give you an oversimplified example:

Two Fetchers, elusively alike

Their code was pretty similar, and the whole logic of handling the response, and re-scheduling the async task was duplicated, so we have refactored it in a separate collaborator object in the production code.

Then we thought: “Well, we refactored duplication in the production code. There is bound to be a duplication in the test code. Let’s get it DRYed as well!”

Not so fast!

When we started reading two test suites side-by-side, it turned out that they don’t look too much alike. It was hard to spot and isolate the duplication.

They were written in a hierarchical style, and they were leveraging the full power of nested contexts.

Here is a reduced example for the first test (in Kotlin):

So the test suite starts with the root context. Here is where you can set the global defaults for your test suite, and override some of them in the nested contexts.

This is good if you have a typical “happy path” scenario, and then you have more “special cases,” for which you’ll use nested contexts.

Notice that we’re mocking the HTTP client to respond with an “accepted” status:

And then in one of the nested contexts, we override the response status to “successful”:

With that all in mind, take a look at the second test suite:

As you can see, the author of this test suite have chosen a different response status as the default “happy-path” scenario — “successful”:

(and used a different mocking style on top of that)

And the nested context overrides the response status to “accepted” status:

Now, with this example, you could figure out how to refactor it quickly enough, after inverting the default and custom scenarios for one of the test suites, right?

This example is elementary. What we had was more complicated. Imagine that your async polling and retrying logic (what we were trying to refactor) was dependent on 4 factors:

  • You’ll have 4 levels of nested contexts describing each element as a part of the overall scenario;
  • Each context may choose a different default case as opposed to which ones should be overridden in the inner context.

As you can see, this can quickly become a mess, and very hard to refactor.

If we had a classic flat test structure, we’d have a little bit of duplication in the test suite, but it would be so much easier to compare test suites side-by-side and refactor them.

Let me show you an example:

The second test suite will now look very similar (not worth showing here, but you can see the gist if you like).

As you can see, refactoring the duplication between two test suites now is almost a no-brainer.

Do you like to learn more about testing and TDD in Kotlin?

I have written a 4-part (350-pages total) “Ultimate Tutorial: Getting Started With Kotlin” (+ more to come), and you can get it as a free bonus by becoming a member of my monthly newsletter.

On top of just Kotlin, it is full of goodies like TDD, Clean Code, Software Architecture, Business Impacts, 5 WHYs, Acceptance Criteria, Personas, and more.

— Sign up here and start learning how to build full-fledged Kotlin applications with TDD!


And as we’ve noticed even strength of the style might let you shoot yourself in the foot. So be careful: with power comes responsibility.

Don’t make your hierarchical tests too complex and too nested!

Your Turn

What tricky situations with both styles do YOU remember? 🤔


Sign up for Get Better Tech Emails via


how hackers start their afternoons. the real shit is on Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Alex Fedorov

Written by

👨‍💻Product Maker | 📊 | Formerly Senior Software Engineer @pivotal 🚀 Hard is better than easy!

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Alex Fedorov

Written by

👨‍💻Product Maker | 📊 | Formerly Senior Software Engineer @pivotal 🚀 Hard is better than easy!

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store