Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/waterlink/spec2.cr
Enhanced `spec` testing library for [Crystal](http://crystal-lang.org/).
https://github.com/waterlink/spec2.cr
Last synced: 12 days ago
JSON representation
Enhanced `spec` testing library for [Crystal](http://crystal-lang.org/).
- Host: GitHub
- URL: https://github.com/waterlink/spec2.cr
- Owner: waterlink
- License: mit
- Created: 2015-06-08T22:43:50.000Z (about 9 years ago)
- Default Branch: master
- Last Pushed: 2018-03-03T11:31:32.000Z (over 6 years ago)
- Last Synced: 2024-04-15T03:18:23.048Z (3 months ago)
- Language: Crystal
- Size: 123 KB
- Stars: 102
- Watchers: 7
- Forks: 22
- Open Issues: 16
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Lists
- awesome-crystal - spec2.cr - Enhanced testing library (Testing)
- awesome-crystal - spec2.cr - Enhanced testing library (Testing)
README
# spec2 [![Build Status](https://travis-ci.org/waterlink/spec2.cr.svg)](https://travis-ci.org/waterlink/spec2.cr)
Enhanced `spec` testing library for [Crystal](http://crystal-lang.org/).
## Example
```crystal
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`
```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` and `after` blocks for example
group
- Ability to define `let`, `let!`, `subject` and `subject!`
for example group## Roadmap
### `1.0`
- [ ] Configuration through CLI interface.
- [ ] Filters.
- [ ] Shared examples and example groups.## Usage
```crystal
require "spec2"
```### Top-level describe
```crystal
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:```crystal
include Spec2::GlobalDSL# and then:
describe Greeting do
# ...
end
```### Writing examples
```crystal
Spec2.describe "some tests" do
it "is a test name here" do
# .. this is the example here ..
endpending "is a pending test here" do
# .. this example will not be executed ..
end
end
```### `Expect` syntax
```crystal
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`:```crystal
Spec2.enable_should_on_object
```### List of builtin matchers
- `eq("hello, world")` - asserts actual is equal to expected
- `raise_error(ErrorClass [, message_matcher])` - checks if block raises
expected error
- `be(42)` - asserts actual is the same as expected
- `match(/hello .+/)` - asserts actual is matching provided regexp
- `be_true` - asserts actual is equal `true`
- `be_false` - asserts actual is equal `false`
- `be_truthy` - asserts actual is not `nil` or `false`
- `be_falsey` - asserts actual is `nil` or `false`
- `be_nil` - asserts actual is equal `nil`
- `be_close(42, 0.01)` - asserts actual is in delta-proximity of expected
- `expect(42).to_be < 45` - asserts arbitrary method call on actual to be
truthy
- `be_a(String)` - asserts actual to be of expected type (uses `is_a?`)### Random order
```crystal
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:
```crystal
Spec2.configure_order(MyOrder)
```Class `MyOrder` should implement `Order` protocol and `Order::Factory` class
protocol ([see it here](src/order.cr)).
See also [a random order implementation](src/orders/random.cr).### No color mode
```crystal
Spec2.nocolor# this is what happens under the hood
Spec2.configure_output(Spec2::Outputs::Nocolor)
```To configure your own custom output you can use:
```crystal
Spec2.configure_output(MyOutput)
```Class `MyOutput` should implement `Output` protocol and `Output::Factory` class
protocol ([see it here](src/output.cr)).
See also [a default colorful output implementation](src/outputs/default.cr).### Documentation reporter
```crystal
Spec2.doc# this is what happens under the hood
Spec2.configure_reporter(Spec2::Reporters::Doc)
```To configure your own custom reporter you can use:
```crystal
Spec2.configure_reporter(MyReporter)
```Class `MyReporter` should implement `Reporter` protocol and `Reporter::Factory`
class protocol ([see it here](src/reporter.cr)).
See also [a default reporter implementation](src/reporters/default.cr).If you are creating a custom reporter, you might want to use `ElapsedTime`
class to report elapsed time for the test suite. Example usage:```crystal
output.puts "Finished in #{::Spec2::ElapsedTime.new.to_s}"
```### Configuring custom Runner
```crystal
Spec2.configure_runner(MyRunner)
```Class `MyRunner` should implement `Runner` protocol and `Runner::Factory` class
protocol ([see it here](src/runner.cr)).
See also [a default runner implementation](src/runners/default.cr).### `before`
`before` - register a hook that is run before any example in current and all
nested contexts.```crystal
before { .. do some stuff .. }
```### `after`
`after` - register a hook that is run after any successful example in current
and all nested contexts.```crystal
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.```crystal
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.```crystal
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:
```crystal
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.```crystal
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.```crystal
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.```crystal
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.```crystal
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:```crystal
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](src/matcher.cr):
```crystal
class MyMatcher(T, E)
include Spec2::Matcher@actual_inspect : String?
def initialize(@expected : T, @stuff : E)
enddef match(actual)
@actual_inspect = actual.inspect# return true or false here
enddef failure_message
"Expected to be valid #{@stuff.inspect}.
Expected: #{@expected.inspect}.
Actual: #{@actual_inspect}."
enddef failure_message_when_negated
"Expected to be invalid #{@stuff.inspect}.
Expected: #{@expected.inspect}.
Actual: #{@actual_inspect}."
enddef description
"(stuff in #{@expected} #{stuff})"
end
end
```And then, register shortcut helper method to use your matcher.
```crystal
Spec2.register_matcher(stuff) do |stuff, expected|
MyMatcher.new(expected, stuff)
end
```And use it:
```crystal
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` and `crystal unit` to see if tests are green
(or just run `scripts/test` to run them both)
- apply TDD to implement your feature/fix/etc## Contributing
1. Fork it ( https://github.com/waterlink/spec2.cr/fork )
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create a new Pull Request## Contributors
- [waterlink](https://github.com/waterlink) Oleksii Fedorov - creator, maintainer