TDD Basics : Canonical Test Structure


  • To practice Canonical Test Structure, Given, When, Then


According to the dictionary, the term canonical is defined as:

Mathematics : relating to a general rule or standard formula.

In our case, the following three steps is a standard forumula for writing any test:

Step 1 - Given : Precondition. The System is in a known state.
Step 2 - When  : Exercise the System Under Test (SUT)
Step 3 - Then  : Postcondition. Check the outcome is as expected.


Step 1

Create a file stack_spec.rb with the following contents:

require_relative 'stack'

describe Stack do
  it 'should push a given item' do
    stack =


    stack.size.should == 1

  it 'should pop from the stack' do
    stack =

    result = stack.pop

    result.should == 2
    stack.size.should == 0

We have two specs that describe the behavior of push and pop operations of Stack.

Step 2

Create a file stack.rb with a simple implementation that can push and pop as follows:

class Stack
  def initialize
      @elements = []

  def push(item)
      @elements << item

  def   pop

  def size

Step 3

Run the specs.

$rspec stack_spec.rb

You should see them all pass.


Instead of thinking about How do I write a test?. Ask yourself the following questions:

  • What is the given condition?
  • How do I exercise the system under test?
  • How do I verify the outcome?

The answers to these questions will help you write the test. These questions correspond to each of the step in the Canonical Test Structure.

For example, if you have a class called Car, you need to have fuel in order to drive the car. The given condition in this case is that is has fuel. The drive is the behavior you are testing so you invoke drive() on the instance of a car in order to exercise the system under test. When you drive you expect to travel. So, you can verify the outcome by checking the distance traveled for a given amount of time and average speed.

Identifying Given, When, Then

Here is an example of how to identify Given, When, Then in a test. Copy the following givenwhenthen.rb to canonical exercise directory.

def Given

def When

def Then

These are just methods in the givenwhenthen.rb file. Do not create a class for givenwhenthen.rb. The following code identifies the three steps for the stack_spec.rb:

require_relative 'stack'
require_relative 'given_when_then'

describe Stack do
  it 'should push a given item' do
    Given { @stack = }

    When { @stack.push(1) }

    Then { @stack.size.should == 1}

Step 4

Run the stack_spec.rb. It should pass.


This is an example for State Verification. We check the state of the system after we exercise the SUT for correctness.


In this article we used the training wheels givenwhenthen.rb to practice identifying the three steps of the canonical test structure. It is also called as Arrange Act Assert or AAA. These training wheels are not required once you understand the concept.


Identify the Given, When, Then steps for the second spec:

it 'should pop from the stack'.


What if the method does many things things that needs to be tested?

Ideally a method should be small and do just one thing. If a method has three steps with different scenarios, then you will write three different specs for each scenario.

What if I want to test push and pop in one test?

The structure of the test is Arrange, Act, Assert. There should be only one Arrange, one Act and one Assert per test. In this case you would have multiple of each of these steps. So it does not follow the best practice.

Why do we need just one AAA in our test?

Because if you had multiple of each of the steps, you would have the state at the end of the first assertion interact with the state at the end of the second assertion. Ideally we want each test to be isolated.

This is discussed in depth in xUnit Test Patterns by Gerard Mesaros. Here is a concise version of his discussion on this topic.

Principle: Verify One Condition per Test

Also known as: Single Condition Test

When one assertion fails, the rest of the test is not executed. This makes it hard to achieve Defect Localization. A single failed assertion will cause the test to stop running and the rest of the test will provide no data on what works and what doesn't.

What is isolation?

Isolation means that the state is clean in the beginning of each test and it cleans up the state at the end of each test. This makes our tests independent. Making tests independent is a good practice. When the tests are independent we can run them in any order and they will pass.

Related Articles

Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.