Skip to content

abstract struct Athena::Spec::TestCase
inherits Struct #

ASPEC::TestCase provides a Spec compliant alternative DSL for creating unit and integration tests. It allows structuring tests in a more OOP fashion, with the main benefits of reusability and extendability.

This type can be extended to share common testing logic with groups of similar types. Any tests defined within a parent will run for each child test case. abstract def, super, and other OOP features can be used as well to reduce duplication. Some additional features are also built in, such as the DataProvider.

Note

This is NOT a standalone testing framework. Everything boils down to standard describe, it, and/or pending blocks.

A test case consists of a struct inheriting from self, optionally with an #initialize method in order to initialize the state that should be used for each test.

A test is a method that starts with test_, where the method name is used as the description. For example, test_some_method_some_context becomes "some method some context". Internally each test method maps to an it block. All of the stdlib's Spec assertions methods are available, in addition to #pending! and #fail.

A method may be focused by either prefixing the method name with an f, or applying the Focus annotation.

A method may be marked pending by either prefixing the method name with a p, or applying the Pending annotation. Internally this maps to a pending block.

Tags may be applied to a method via the Tags annotation.

The Tags, Focus, and Pending annotations may also be applied to the test case type as well, with a similar affect.

Example#

# Require the stdlib's spec module.
require "spec"

# Define a class to test.
class Calculator
  def add(v1, v2)
    v1 + v2
  end

  def subtract(v1, v2)
    raise NotImplementedError.new "TODO"
  end
end

# An example test case.
struct ExampleSpec < ASPEC::TestCase
  @target : Calculator

  # Initialize the test target along with any dependencies.
  def initialize : Nil
    @target = Calculator.new
  end

  # All of the stdlib's `Spec` methods can be used,
  # plus any custom methods defined in `ASPEC::Methods`.
  def test_add : Nil
    @target.add(1, 2).should eq 3
  end

  # A pending test.
  def ptest_subtract : Nil
    @target.subtract(10, 5).should eq 5
  end

  # Private/protected methods can be used to reduce duplication within the context of single test case.
  private def helper_method
    # ...
  end
end

Inheritance#

Inheritance can be used to build reusable test cases for groups of similar objects

abstract struct SomeTypeTestCase < ASPEC::TestCase
  # Require children to define a method to get the object.
  protected abstract def get_object : Calculator

  # Test cases can use the abstract method for tests common to all test cases of this type.
  def test_common : Nil
    obj = self.get_object

    # ...
  end
end

struct CalculatorTest < SomeTypeTestCase
  protected def get_object : Calculator
    Calculator.new
  end

  # Additional tests specific to this type.
  def test_specific : Nil
    # ...
  end
end

Data Providers#

A DataProvider can be used to reduce duplication, see the corresponding annotation or more information.

struct DataProviderTest < ASPEC::TestCase
  # Data Providers allow reusing a test's multiple times with different input.
  @[DataProvider("get_values")]
  def test_squares(value : Int32, expected : Int32) : Nil
    (value ** 2).should eq expected
  end

  # Returns a hash where the key represents the name of the test,
  # and the value is a Tuple of data that should be provided to the test.
  def get_values : Hash
    {
      "two"   => {2, 4},
      "three" => {3, 9},
    }
  end
end
# Run all the test cases
ASPEC.run_all # =>
# ExampleSpec
#   add
#   subtract
#   a custom method name
# CalculatorTest
#   common
#   specific
# DataProviderTest
#   squares two
#   squares three
#
# Pending:
# ExampleSpec subtract
#
# Finished in 172 microseconds
# 7 examples, 0 failures, 0 errors, 1 pending

Included modules

Athena::Spec::Methods

Constructors#

.new#

Runs before each test.

Used to create the objects that will be used within the tests.

require "spec"
require "athena-spec"

struct ExampleSpec < ASpec::TestCase
  @value : Int32

  def initialize : Nil
    @value = 1
  end

  def test_one : Nil
    @value += 1

    @value # => 2
  end

  def test_two : Nil
    @value # => 1
  end
end

ExampleSpec.run
View source

Class methods#

.run : Nil#

Runs the tests contained within self.

See Athena::Spec.run_all to run all test cases.

View source

Methods#

#after_all : Nil#

Runs once after all tests within self have been executed.

require "spec"
require "athena-spec"

struct ExampleSpec < ASPEC::TestCase
  def after_all : Nil
    puts "This prints only once after anything else"
  end

  def test_one : Nil
    true.should be_true
  end

  def test_two : Nil
    1.should eq 1
  end
end

ExampleSpec.run
View source

#before_all : Nil#

Runs once before any tests within self have been executed.

Can be used to initialize objects common to every test, but that do not need to be reset before running each test.

require "spec"
require "athena-spec"

struct ExampleSpec < ASPEC::TestCase
  def before_all : Nil
    puts "This prints only once before anything else"
  end

  def test_one : Nil
    true.should be_true
  end

  def test_two : Nil
    1.should eq 1
  end
end

ExampleSpec.run
View source

#tear_down : Nil#

Runs after each test.

Can be used to cleanup data in between tests, such as releasing a connection or closing a file.

require "spec"
require "athena-spec"

struct ExampleSpec < ASPEC::TestCase
  @file : File

  def initialize : Nil
    @file = File.new "./foo.txt", "w"
  end

  def tear_down : Nil
    @file.close
  end

  def test_one : Nil
    @file.path # => "./foo.txt"
  end
end

ExampleSpec.run
View source