Testing colored output with Cucumber

I am improving a Command line app to manage my todos. I am developing it entirely ‘Behaviour Driven’, using Cucumber and Aruba.

All is pretty straightforward, but I had a hard time testing the colors in the output. Colors are made with Rainbow; which is really neat, but sometimes a little too smart. Rainbow detects when it outputs to something that cannot handle colors and turns them off. The solution turned out to be really simple though.

Lets start with a simple script that outputs some Rastafari

#!/usr/bin/env ruby

require "thor"
require "rainbow"

class Example < Thor
  desc "example", "an example task"
  def example
    puts "Yah!".color(:red)
    puts "...".color(:black).bright
    puts "Rasta-".color(:yellow)
    puts "fari".color(:green)
  end
end
Example.start()

Running this, results in:

Example output

But piping this into a file, or for example less, shows no colors; This is a useful feature built into Rainbow. When testing with cucumber, the colors are gone too:

Feature: Example
  Scenario: Yah!
    When I run `example example`
    Then it should pass with:
      """
      Yah!
      ...
      """

This passes, but does not test any colors. First thing is to tell Aruba/Cucumber to not strip the colors, ansi-codes, with an @ansi tag.

Next thing is to tell Rainbow to output colors regardless of where it outputs to. We need to do do this in the application itself, by making the application a little more testable. However, Aruba strips the colors for a reason: it is really hard to test when all your output is littered with ANSI escape codes. You really only want to force Rainbow to output them when you are testing for colors.

#...
class Example < Thor
  def initialize(*args)
    super
    Sickill::Rainbow.enabled = true if ENV["FORCE_COLORS"] == "TRUE"
  end
  #....
end

This allows you to force coloring when testing or running by setting the variable, like so export FORCE_COLORS=TRUE; ./bin/example example. A step could them look like “When I run export FORCE_COLORS=TRUE; ./bin/example example”.

More usefull however, is that we can set this variable in cucumber for all the @ansi-tagged scenario’s. In a support-file features/support/ansi.rb:

Before('@ansi') do
  ENV["FORCE_COLORS"] = "TRUE"
end

With the scenario tagged @ansi, it fails: expected "\e[31mYah!\e[0m\n\e[30m\e[1m...\e[0m\n\e[33mRasta-\e[0m\n\e[32mfari\e[0m\n" to include "Yah! .... Good.

Testing against strings like “\e[31m”, however, is both error-prone and unreadable. A simple new step definition, in which we add the ansi-escape codes, using Rainbow, to the to-be-tested string. Which allows us to test colors really easy.

The features/support/ansi.rb should include “rainbow”.

Then /^it should output "([^"]*)" in "([^"]*)"$/ do |string, color|
  assert_partial_output(string.color(color.to_sym), all_output)
end
Feature: Example

  @ansi
  Scenario: Yah!
    When I run `example example`
    Then it should output "Yah!" in "red"

Readable, easy testing of your colored output!

Woodcut from Doré. Purely illustrative
Doré Woodcut. Its only function is to make the layout look better. And these images are really nice themselves

About the author: Bèr Kessels is an experienced webdeveloper with a great passion for technology and Open Source. A golden combination to implement that technology in a good and efficient way. Follow @berkes on Mastodon. Or read more about Bèr.