spec2~maiha
spec2
Enhanced spec
testing library for Crystal.
Example
Spec2.describe Greeting do
subject { Greeting.new }
describe "#greet" do
context "when name is world" do
let(name) { "world" }
it "greets the world" do
expect(subject.greet(name)).to eq("Hello, world")
end
end
end
end
Installation
Add it to shard.yml
dependencies:
spec2:
github: waterlink/spec2.cr
version: ~> 0.9
Goals
- No global scope pollution
- No
Object
pollution - Ability to run examples in random order
- Ability to specify
before
andafter
blocks for example group - Ability to define
let
,let!
,subject
andsubject!
for example group
Roadmap
1.0
- [ ] Configuration through CLI interface.
- [ ] Filters.
- [ ] Shared examples and example groups.
Usage
require "spec2"
Top-level describe
Spec2.describe MySuperLibrary do
describe Greeting do
# .. example groups and examples here ..
end
end
If you have test suite written for Spec
and you don't want to prefix each
top-level describe with Spec2.
, you can just include Spec::GlobalDSL
globally:
include Spec2::GlobalDSL
# and then:
describe Greeting do
# ...
end
Writing examples
Spec2.describe "some tests" do
it "is a test name here" do
# .. this is the example here ..
end
pending "is a pending test here" do
# .. this example will not be executed ..
end
end
Expect
syntax
expect(greeting.for("john")).to eq("hello, john")
If you have big codebase that runs on Spec
, you can use this to
enable #should
and #should_not
on Object
:
Spec2.enable_should_on_object
List of builtin matchers
eq("hello, world")
- asserts actual is equal to expectedraise_error(ErrorClass [, message_matcher])
- checks if block raises expected errorbe(42)
- asserts actual is the same as expectedmatch(/hello .+/)
- asserts actual is matching provided regexpbe_true
- asserts actual is equaltrue
be_false
- asserts actual is equalfalse
be_truthy
- asserts actual is notnil
orfalse
be_falsey
- asserts actual isnil
orfalse
be_nil
- asserts actual is equalnil
be_close(42, 0.01)
- asserts actual is in delta-proximity of expectedexpect(42).to_be < 45
- asserts arbitrary method call on actual to be truthybe_a(String)
- asserts actual to be of expected type (usesis_a?
)
Random order
Spec2.random_order
# this is what happens under the hood
Spec2.configure_order(Spec2::Orders::Random)
To configure your own custom order you can use:
Spec2.configure_order(MyOrder)
Class MyOrder
should implement Order
protocol and Order::Factory
class
protocol (see it here).
See also a random order implementation.
No color mode
Spec2.nocolor
# this is what happens under the hood
Spec2.configure_output(Spec2::Outputs::Nocolor)
To configure your own custom output you can use:
Spec2.configure_output(MyOutput)
Class MyOutput
should implement Output
protocol and Output::Factory
class
protocol (see it here).
See also a default colorful output implementation.
Documentation reporter
Spec2.doc
# this is what happens under the hood
Spec2.configure_reporter(Spec2::Reporters::Doc)
To configure your own custom reporter you can use:
Spec2.configure_reporter(MyReporter)
Class MyReporter
should implement Reporter
protocol and Reporter::Factory
class protocol (see it here).
See also a default reporter implementation.
If you are creating a custom reporter, you might want to use ElapsedTime
class to report elapsed time for the test suite. Example usage:
output.puts "Finished in #{::Spec2::ElapsedTime.new.to_s}"
Configuring custom Runner
Spec2.configure_runner(MyRunner)
Class MyRunner
should implement Runner
protocol and Runner::Factory
class
protocol (see it here).
See also a default runner implementation.
before
before
- register a hook that is run before any example in current and all
nested contexts.
before { .. do some stuff .. }
after
after
- register a hook that is run after any successful example in current
and all nested contexts.
after { .. do some stuff .. }
let
let(name) { value }
- register a binding of certain value
to name
. Lazy:
provided block will only be evaluated when needed in example and only once per
example.
let(answer) { 42 }
it "is correct answer" do
expect(answer).to eq(42)
end
let!
let(name) { value }
- register a binding of certain value
to name
. It is
not lazy: provided block will be evaluated before each example exactly once.
let!(answer) { 42 }
it "is correct answer" do
expect(answer).to eq(42)
end
described_class
For describe ...
blocks, that describe a class, there is a shortcut to reference that class:
describe Example do
it "can be created" do
expect(described_class.new.greet).to eq("hello world")
# instead of `Example.new.greet`.
end
end
subject
subject { value }
- register a subject of your test with provided value
.
Lazy.
subject { Stuff.new }
it "works" do
expect(subject.answer).to eq(42)
end
subject(name) { value }
- registers a named subject of your test with
provided value
with provided name
. Lazy.
subject(stuff) { Stuff.new }
it "works" do
expect(stuff.answer).to eq(42)
end
subject!
subject! { value }
- register a subject of your test with provided value
.
It is not lazy.
subject! { Stuff.new }
it "works" do
expect(subject.answer).to eq(42)
end
subject!(name) { value }
- registers a named subject of your test with
provided value
with provided name
. It is not lazy.
subject!(stuff) { Stuff.new }
it "works" do
expect(stuff.answer).to eq(42)
end
delayed
Use delayed { ... }
to verify expectations after test example and its after
hooks finish. Example:
it "does something interesting eventually" do
delayed { expect(value).to eq(42) }
# .. do something else, that should eventually lead to value == 42 ..
end
Custom matchers
First, define your matcher implementing this protocol:
class MyMatcher(T, E)
include Spec2::Matcher
@actual_inspect : String?
def initialize(@expected : T, @stuff : E)
end
def match(actual)
@actual_inspect = actual.inspect
# return true or false here
end
def failure_message
"Expected to be valid #{@stuff.inspect}.
Expected: #{@expected.inspect}.
Actual: #{@actual_inspect}."
end
def failure_message_when_negated
"Expected to be invalid #{@stuff.inspect}.
Expected: #{@expected.inspect}.
Actual: #{@actual_inspect}."
end
def description
"(stuff in #{@expected} #{stuff})"
end
end
And then, register shortcut helper method to use your matcher.
Spec2.register_matcher(stuff) do |stuff, expected|
MyMatcher.new(expected, stuff)
end
And use it:
describe "stuff" do
it "is valid stuff" do
expect(something).to stuff(some_stuff, "expected stuff")
end
end
Development
After you forked the repo:
- run
crystal deps
to install dependencies - run
crystal spec
andcrystal unit
to see if tests are green (or just runscripts/test
to run them both) - apply TDD to implement your feature/fix/etc
Contributing
- Fork it ( https://github.com/waterlink/spec2.cr/fork )
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
Contributors
- waterlink Oleksii Fedorov - creator, maintainer