DEV Community

Cover image for Organizing Tests with Clarity: the Unfolding Tests Technique
Augusto Hubert
Augusto Hubert

Posted on

Organizing Tests with Clarity: the Unfolding Tests Technique

[pt-BR] Existe uma versão desse artigo em pt-BR, que você pode acessar aqui

In recent years, much of my work has involved helping teams expand and optimize their automated test suites. And one thing that comes up repeatedly is the same kind of problem: the tests exist, but they are hard to understand.

No one knows exactly what is being tested, where certain scenarios are, or whether the logical branches are truly covered. As a result, you see coverage gaps, redundant tests, and a lot of time wasted trying to understand what already exists.

On many occasions, when pairing with less experienced developers, I notice a recurring pattern: they spend a long time scrolling up and down the test file, looking for code that looks “similar” to what they want to write. The idea is to place the new test “near the other similar ones.” This habit is understandable, but it’s also a symptom of a structural problem: if you need to “guess” where the test should go, the file structure isn’t clear enough.

Over time, I developed a technique that helps me organize and visualize test structures. It’s simple and practical, and it leverages a feature that almost every modern text editor has: code folding.

I call this technique Unfolding Tests.

The examples in this article use Ruby on Rails and RSpec, but the same principles apply to any other testing framework.


The idea behind Unfolding Tests

The idea is to use code block folding as both a reading and writing tool. In VS Code, for example, you can fold and unfold blocks such as describe, context, and it.

To start, use the Fold All command (on macOS: Cmd + Shift + P; on Windows: Ctrl + Shift + P) and type “Fold All.”

This technique is grounded in the idea of reducing cognitive load. When everything is folded, you see only the skeleton of the test, which lets you quickly understand what’s being tested and how the scenarios are organized, without reading the implementation details. That way, the focus becomes the intent, rather than every line of code.

From there, I gradually expand the blocks and come to understand:

  • What’s being tested (describe)
  • What the possible scenarios are (context)
  • What the expectations are in each scenario (it)
  • And which setups are used at each level

This hierarchical view provides a much clearer reading of the system’s behavior under test. What’s most interesting is that the same process also works when writing tests.


Writing tests

When I start a new test file, I don’t think in lines of code. I think in the structure I want to see when folded.

First, I create the skeleton of the test:

RSpec.describe UserPolicy do
  context 'when user is admin' do
    it 'allows access'
  end

  context 'when user is not admin' do
    it 'denies access'
  end
end
Enter fullscreen mode Exit fullscreen mode

When that file is fully folded, what I see is something like:

UserPolicy
  when user is admin
    allows access
  when user is not admin
    denies access
Enter fullscreen mode Exit fullscreen mode

This is already enough to understand what’s being tested and what the scenarios are.

By the way, this view is very similar to the RSpec output when you use the --format documentation option.

If you’re not using that format yet, you should. It turns test execution into an almost narrative reading, showing exactly what each test describes, in the same hierarchy you defined in the describe, context, and it blocks.

After that, I unfold block by block and fill in the details: setups, mocks, expectations, and so on.


The hierarchy of setups

One of the most important parts of Unfolding Tests is respecting the hierarchy between the blocks and their setups.

  • Under each describe, there should be a setup with the general prerequisites for all scenarios inside that block.
  • Under each context, there should be a setup with the prerequisites specific to that scenario. Your setup here must provide the conditions that make the context description true.
  • The test code itself should always be inside the it block, which is the lowest level in the structure where expectations are verified.
describe 'Listing orders' do
  let(:admin_user) { create(:user, :admin) }

  before do
    sign_in(admin_user)
    visit orders_path
  end

  context 'when there are no orders' do
    before { Order.delete_all } 

    it 'shows the empty state message' do
      expect(page).to have_content('No orders found')
    end
  end

  context 'when there are orders' do
    let!(:orders) { create_list(:order, 2) }

    it 'lists all orders' do
      expect(page)
        .to have_text(orders.first.id)
        .and have_text(orders.second.id)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

If you fold this file, even without seeing the implementation details, you can understand the purpose of the test, the scenarios it covers, and the expected behavior in each case. Then, when you unfold each block, you reveal the necessary details: first the setup, then the test.


Contexts as logical branches

An important principle of this technique is that each logical branch deserves its own context. If a variable can take three states (for example, full, partial, empty), then you should have three contexts, one for each case.

Example:

context 'when report is full' do
  # ...
end

context 'when report is partial' do
  # ...
end

context 'when report is empty' do
  # ...
end
Enter fullscreen mode Exit fullscreen mode

This helps ensure that the tests truly explore all relevant possibilities.

And more: to me, it doesn’t make sense to have a context without others that represent opposing states. If there’s a context 'when user is admin', there should also be a context 'when user is not admin'. These pairs make it clear that the test covers both sides of the logic, and that’s easy to see when the file is folded.


Why this works

Unfolding Tests turns the act of reading and writing tests into something visual and incremental.
You see the structure before you see the details, which helps you:

  • Quickly understand the scope of the file
  • Identify redundancies and gaps
  • Ensure that each logical path has its counterpart
  • Write tests that also serve as living documentation

In the end, a good test is also a good form of documentation. And the clearer the intent expressed in the structure, the easier it will be for anyone to understand the expected behavior of the system.


Tips to start applying

  1. Fold everything (Fold All).
    Start with the test file completely folded. This gives you a clear overview of the structure, the main blocks, and the existing scenarios.

  2. Unfold gradually.
    Expand the outer describe blocks first to understand what’s being tested. Then open the context blocks to see the scenarios, and finally the it blocks to look at the expectations. This gradual progression helps you understand the test layer by layer, without overloading your mind with unnecessary details right away.

  3. Check if the structure makes sense when folded.
    If, with everything folded, you can’t tell what the file is testing or which scenarios it covers, that’s a sign the structure needs to be revisited.

  4. When writing new tests, think about the structure first.
    Build the skeleton with describe, context, and it before writing any code.

  5. Use context to express logical variations.
    Whenever possible, write contexts in pairs — for example, “when user is admin” and “when user is not admin.”

  6. Respect the setup hierarchy.
    Place general before and let definitions inside the describe, and specific setups inside each context.

  7. Only then write the test code (it).
    Once the structure and prerequisites are clear, add the actual test code.


Unfolding Tests is a simple way to bring clarity and intent to your tests.

It forces you to think about structure before details and to treat each scenario as part of a cohesive whole. In the end, the benefit is twofold: tests become easier to understand, and the code is less likely to get lost among confusing or redundant cases.

If you often get lost in large test files, try folding everything and then unfolding gradually. It’s a very simple technique, yet it’s impressive how much it changes the way you read and write tests.

Top comments (0)