Skip to content

Commit

Permalink
Add docs to expectations methods (#5092)
Browse files Browse the repository at this point in the history
* Add docs to expectations methods

* improve wording and example code for `be` matchers

* Add documentation to other Spec methods

* Improve wording in spec preface
  • Loading branch information
straight-shoota authored and RX14 committed Oct 26, 2017
1 parent 4e079f9 commit 844cc6e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 11 deletions.
21 changes: 11 additions & 10 deletions src/spec.cr
@@ -1,13 +1,16 @@
require "./spec/dsl"

# Crystal's built-in testing library.
# Crystal's built-in testing library. It provides a structure for writing executable examples
# of how your code should behave. A domain specific language allows you to write them in a way similar to natural language.
#
# The Crystal compiler has a `spec` command with tools to constrain which examples get run and tailor the output.
#
# A basic spec looks something like this:
#
# ```
# require "spec"
#
# describe "Array" do
# describe Array do
# describe "#size" do
# it "correctly reports the number of elements in the Array" do
# [1, 2, 3].size.should eq 3
Expand Down Expand Up @@ -45,21 +48,19 @@ require "./spec/dsl"
# returned. See the example above for details.
#
# By convention, specs live in the `spec` directory of a project. You can compile
# and run the specs of a project by running:
# and run the specs of a project by running `crystal spec`.
#
# ```shell
# # Run all specs in files matching spec/**/*_spec.cr
# crystal spec
# ```
#
# You can also compile and run individual spec files by providing their path:
# # Run all specs in files matching spec/my/test/**/*_spec.cr
# crystal spec spec/my/test/
#
# ```shell
# # Run all specs in spec/my/test/file_spec.cr
# crystal spec spec/my/test/file_spec.cr
# ```
#
# In addition, you may run individual specs by providing a line number:
#
# ```shell
# # Run the spec or group defined in line 14 of spec/my/test/file_spec.cr
# crystal spec spec/my/test/file_spec.cr:14
# ```
module Spec
Expand Down
38 changes: 37 additions & 1 deletion src/spec/expectations.cr
Expand Up @@ -200,62 +200,92 @@ module Spec
end
end

# This module defines a number of methods to create expectations, which are
# automatically included into the top level namespace.
#
# Expectations are used by `Spec::ObjectExtensions#should` and `Spec::ObjectExtensions#should_not`.
module Expectations
# Creates an `Expectation` that passes if actual equals *value* (`==`).
def eq(value)
Spec::EqualExpectation.new value
end

# Creates an `Expectation` that passes if actual and *value* are identical (`.same?`).
def be(value)
Spec::BeExpectation.new value
end

# Creates an `Expectation` that passes if actual is true (`== true`).
def be_true
eq true
end

# Creates an `Expectation` that passes if actual is false (`== false`).
def be_false
eq false
end

# Creates an `Expectation` that passes if actual is truthy (neither `nil` nor `false`).
def be_truthy
Spec::BeTruthyExpectation.new
end

# Creates an `Expectation` that passes if actual is falsy (`nil` or `false`).
def be_falsey
Spec::BeFalseyExpectation.new
end

# Creates an `Expectation` that passes if actual is nil (`== nil`).
def be_nil
Spec::BeNilExpectation.new
end

# Creates an `Expectation` that passes if actual is within *delta* of *expected*.
def be_close(expected, delta)
Spec::CloseExpectation.new(expected, delta)
end

# Returns a factory to create a comparison `Expectation` that:
#
# * passes if actual is lesser than *value*: `be < value`
# * passes if actual is lesser than or equal *value*: `be <= value`
# * passes if actual is greater than *value*: `be > value`
# * passes if actual is greater than or equal *value*: `be >= value`
def be
Spec::Be
end

# Creates an `Expectation` that passes if actual matches *value* (`=~`).
def match(value)
Spec::MatchExpectation.new(value)
end

# Passes if actual includes *expected*. Works on collections and `String`.
# Creates an `Expectation` that passes if actual includes *expected* (`.includes?`).
# Works on collections and `String`.
def contain(expected)
Spec::ContainExpectation.new(expected)
end

# Creates an `Expectation` that passes if actual is of type *type* (`is_a?`).
macro be_a(type)
Spec::BeAExpectation({{type}}).new
end

# Runs the block and passes if it raises an exception of type *klass*.
#
# It returns the rescued exception.
macro expect_raises(klass)
expect_raises({{klass}}, nil) do
{{yield}}
end
end

# Runs the block and passes if it raises an exception of type *klass* and the error message matches.
#
# If *message* is a string, it matches if the exception's error message contains that string.
# If *message* is a regular expression, it is used to match the error message.
#
# It returns the rescued exception.
macro expect_raises(klass, message, file = __FILE__, line = __LINE__)
%failed = false
begin
Expand Down Expand Up @@ -297,12 +327,18 @@ module Spec
end

module ObjectExtensions
# Validates an expectation and fails the example if it does not match.
#
# See `Spec::Expecations` for available expectations.
def should(expectation, file = __FILE__, line = __LINE__)
unless expectation.match self
fail(expectation.failure_message(self), file, line)
end
end

# Validates an expectation and fails the example if it matches.
#
# See `Spec::Expecations` for available expectations.
def should_not(expectation, file = __FILE__, line = __LINE__)
if expectation.match self
fail(expectation.negative_failure_message(self), file, line)
Expand Down
42 changes: 42 additions & 0 deletions src/spec/methods.cr
@@ -1,12 +1,40 @@
module Spec::Methods
# Defines an example group that describes a unit to be tested.
# Inside *&block* examples are defined by `#it` or `#pending`.
#
# Several `describe` blocks can be nested.
#
# Example:
# ```
# describe "Int32" do
# describe "+" do
# it "adds" { (1 + 1).should eq 2 }
# end
# end
# ```
def describe(description, file = __FILE__, line = __LINE__, &block)
Spec::RootContext.describe(description.to_s, file, line, &block)
end

# Defines an example group that establishes a specifc context,
# like *empty array* versus *array with elements*.
# Inside *&block* examples are defined by `#it` or `#pending`.
#
# It is functionally equivalent to `#describe`.
def context(description, file = __FILE__, line = __LINE__, &block)
describe(description.to_s, file, line, &block)
end

# Defines a concrete test case.
#
# The test is performed by the block supplied to *&block*.
#
# Example:
# ```
# it "adds" { (1 + 1).should eq 2 }
# ```
#
# It is usually used inside a `#describe` or `#context` section.
def it(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block)
return unless Spec.matches?(description, file, line, end_line)

Expand All @@ -28,6 +56,17 @@ module Spec::Methods
end
end

# Defines a pending test case.
#
# *&block* is never evaluated.
# It can be used to describe behaviour that is not yet implemented.
#
# Example:
# ```
# pending "check cat" { cat.alive? }
# ```
#
# It is usually used inside a `#describe` or `#context` section.
def pending(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block)
return unless Spec.matches?(description, file, line, end_line)

Expand All @@ -41,6 +80,9 @@ module Spec::Methods
{{ raise "'assert' was removed: use 'it' instead".id }}
end

# Fails an example.
#
# This method can be used to manually fail an example defined in an `#it` block.
def fail(msg, file = __FILE__, line = __LINE__)
raise Spec::AssertionFailed.new(msg, file, line)
end
Expand Down

0 comments on commit 844cc6e

Please sign in to comment.