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
Objectpollution - Ability to run examples in random order
- Ability to specify
beforeandafterblocks for example group - Ability to define
let,let!,subjectandsubject!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 equaltruebe_false- asserts actual is equalfalsebe_truthy- asserts actual is notnilorfalsebe_falsey- asserts actual isnilorfalsebe_nil- asserts actual is equalnilbe_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 depsto install dependencies - run
crystal specandcrystal unitto see if tests are green (or just runscripts/testto 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