everett1992
everett1992

Reputation: 2671

Readable test names with minitest

I'm using MiniTest on a new Rails project and this is my first time really doing testing. When a test fails the message looks like this

  1) Failure:
Category::when created#test_0002_must have a unique name [/home/caleb/workspace/buzz/test/models/category_test.rb:10]:
Expected: true
  Actual: false

Can you change #test_0002_ to another string to make the error more readable? I know it's a minor issue, but this seems like something that should be supported.

# Example test
require 'test_helper'

describe Category do
  describe 'when created' do
    unique = false
    it 'must not have a unique name' do
      unique.must_equal false
    end
    it 'must have a unique name' do
      unique.must_equal true
    end
  end
end

Upvotes: 1

Views: 1808

Answers (1)

blowmage
blowmage

Reputation: 8984

Well, there is a lot here to cover, so bear with me.

First, the test names are readable. And they are 100% accurate. When you use the spec DSL you are still creating test classes and test methods. In your case, you class is Category::when created and your test method is test_0002_must have a unique name. The # in between them is a very common Ruby idiom for an instance method on a class, which is what your test method is. When you use class or def you can't create classes or methods with spaces in them, but when you create them programmatically you can. When running your code Ruby doesn't care if there are spaces in them or not.

Second, we can affect the display of test class and method. That text comes from a call to Minitest::Test#to_s. Here is what that looks like:

def to_s # :nodoc:
  return location if passed? and not skipped?

  failures.map { |failure|
    "#{failure.result_label}:\n#{self.location}:\n#{failure.message}\n"
  }.join "\n"
end

When the test fails then more info is returned, including the reason for the failure. But the piece we care about is the location. Here is what that looks like:

def location
  loc = " [#{self.failure.location}]" unless passed? or error?
  "#{self.class}##{self.name}#{loc}"
end

Ah, better. On the last line you can clearly see it is printing the class and the method name. If the test is failing the location also includes the filename where the method is defined. Let's break those values out so they aren't inline:

def location
  loc = " [#{self.failure.location}]" unless passed? or error?
  test_class = self.class
  test_name = self.name
  "#{test_class}##{test_name}#{loc}"
end

Okay, a bit clearer. First the test class, then the #, then the test name, then the location if the test is not passing. Now that we have them broken out we can modify them a bit. Let's use / to separate the class namespaces and the test method:

def location
  loc = " [#{self.failure.location}]" unless passed? or error?
  test_class = self.class.to_s.gsub "::", " / "
  test_name = self.name
  "#{test_class} / #{test_name}#{loc}"
end

Great. Now let's remove the test_0002_ from the beginning of the test method. That is added by the spec DSL, and by removing it we can make it match the string passed to the it block:

def location
  loc = " [#{self.failure.location}]" unless passed? or error?
  test_class = self.class.to_s.gsub "::", " / "
  test_name = self.name.to_s.gsub /\Atest_\d{4,}_/, ""
  "#{test_class} / #{test_name}#{loc}"
end

Now, your test output will look like this:

  1) Failure:
Category / when created / must have a unique name [/home/caleb/workspace/buzz/test/models/category_test.rb:10]:
Expected: true
  Actual: false

Minitest is no different than any other Ruby library. The spec DSL is simply a thin wrapper for creating test classes and methods. You can alter the behavior of your test objects to work the way you want them to.

TL;DR Add the following to your test/test_helper.rb file:

class Minitest::Test
  def location
    loc = " [#{self.failure.location}]" unless passed? or error?
    test_class = self.class.to_s.gsub "::", " / "
    test_name = self.name.to_s.gsub /\Atest_\d{4,}_/, ""
    "#{test_class} / #{test_name}#{loc}"
  end
end

Upvotes: 4

Related Questions