Spectus
Expectation library with RFC 2119's requirement levels, and some matchers for Ruby.
Contact
- Home page: https://github.com/fixrb/spectus
- Bugs/issues: https://github.com/fixrb/spectus/issues
- Support: https://stackoverflow.com/questions/tagged/spectus
Rubies
Installation
Spectus is cryptographically signed.
To be sure the gem you install hasn't been tampered with, add my public key (if you haven't already) as a trusted certificate:
$ gem cert --add <(curl -Ls https://raw.github.com/fixrb/spectus/master/certs/gem-fixrb-public_cert.pem)
$ gem install spectus -P HighSecurity
The HighSecurity
trust profile will verify all gems. All of Spectus's dependencies are signed.
Or add this line to your application's Gemfile:
gem 'spectus'
And then execute:
$ bundle
Expectation
An expectation is an assertion that is either true
or false
.
Requirement levels | MUST | SHOULD | MAY |
---|---|---|---|
Implemented & Matched | true |
true |
true |
Implemented & Not matched | false |
true |
false |
Implemented & Exception | false |
false |
false |
Not implemented | false |
false |
true |
Isolation
There are two cases:
- when the requirement level of an expectation ends with
!
, the test is performed in isolation; - when the requirement level of an expectation does not end with
!
, the test is performed without isolation.
Example of test in isolation:
greeting = 'Hello, world!'
it { greeting.gsub!('world', 'Alice') }.MUST! eql 'Hello, Alice!'
# => #<Spectus::Result::Pass:0x007fc03bb56a78 @message="Pass: Expected \"Hello, Alice!\" to eql \"Hello, Alice!\".", @subject=#<Proc:0x007fc03bb57248@(irb):2>, @challenge=#<Defi::Challenge:0x007fc03bb56dc0 @method=:call, @args=[]>, @actual="Hello, Alice!", @expected={:Eql=>"Hello, Alice!"}, @got=true, @error=nil, @level=:High, @negate=false, @valid=true>
greeting # => "Hello, world!"
Example of test without isolation:
greeting = 'Hello, world!'
it { greeting.gsub!('world', 'Alice') }.MUST eql 'Hello, Alice!'
# => #<Spectus::Result::Pass:0x007f94b13de620 @message="Pass: Expected \"Hello, Alice!\" to eql \"Hello, Alice!\".", @subject=#<Proc:0x007f94b13deeb8@(irb):2>, @challenge=#<Defi::Challenge:0x007f94b13dee18 @method=:call, @args=[]>, @actual="Hello, Alice!", @expected={:Eql=>"Hello, Alice!"}, @got=true, @error=nil, @level=:High, @negate=false, @valid=true>
greeting # => "Hello, Alice!"
Results
There are two cases:
- when an expectation is
true
, an instance ofSpectus::Result::Pass
is returned; - when an expectation is
false
, an instance ofSpectus::Result::Fail
is raised.
Both instances share the same interface.
Usage
To begin with, let's include Spectus:
include Spectus
Absolute requirement
Given the "ルビー"
object, when it receives valid_encoding?
method, then it MUST be true
:
it { 'ルビー'.valid_encoding? }.MUST be_true
# => #<Spectus::Result::Pass:0x007fd2791f1a50 @message="Pass: Expected true to be true.", @subject=#<Proc:0x007fd2791f23d8@(irb):1>, @challenge=#<Defi::Challenge:0x007fd2791f2338 @method=:call, @args=[]>, @actual=true, @expected=:BeTrue, @got=true, @error=nil, @level=:High, @negate=false, @valid=true>
The result of the test shows that the spec passed.
Absolute prohibition
Given the "foo"
object, when it receives length
method, then it MUST NOT raise the NoMethodError
exception:
it { 'foo'.length }.MUST_NOT raise_exception NoMethodError
# => #<Spectus::Result::Pass:0x007f94e3408628 @message="Pass: Expected 3 not to raise exception NoMethodError.", @subject=#<Proc:0x007f94e3409050@(irb):1>, @challenge=#<Defi::Challenge:0x007f94e3408fd8 @method=:call, @args=[]>, @actual=3, @expected={:RaiseException=>NoMethodError}, @got=true, @error=nil, @level=:High, @negate=true, @valid=true>
The result of the test shows that the spec passed.
Recommended
Given the BasicObject
object, when it receives superclass
method, then it SHOULD return the explicit blank class NilClass
:
it { BasicObject.superclass }.SHOULD equal NilClass
# => #<Spectus::Result::Pass:0x007fb5ac37cc10 @message="Info: Expected nil to equal NilClass.", @subject=#<Proc:0x007fb5ac37d5c0@(irb):1>, @challenge=#<Defi::Challenge:0x007fb5ac37d520 @method=:call, @args=[]>, @actual=nil, @expected={:Equal=>NilClass}, @got=false, @error=nil, @level=:Medium, @negate=false, @valid=false>
Instead of the expected NilClass
class, its sole instance (which is nil
) was returned.
However, because there isn't any exception, the result of the test shows that the spec passed.
Not recommended
Given the "1"
object, when it receives +(1)
method, then it SHOULD NOT return the "11"
value:
it { '1' + 1 }.SHOULD_NOT eql '11'
# Spectus::Result::Fail: Error: no implicit conversion of Fixnum into String (TypeError).
# from (irb):1
# from ./bin/console:7:in `<main>'
There was a TypeError
exception, the result of the test shows that the spec failed.
Optional
Given the "foo"
object, when it receives blank?
method, then it MAY be false
:
it { 'foo'.blank? }.MAY be_false
# => #<Spectus::Result::Pass:0x007fad1d057130 @message="Info: undefined method `blank?' for \"foo\":String (NoMethodError).", @subject=#<Proc:0x007fad1d057dd8@(irb):1>, @challenge=#<Defi::Challenge:0x007fad1d057ce8 @method=:call, @args=[]>, @actual=nil, @expected=:BeFalse, @got=nil, @error=#<NoMethodError: undefined method `blank?' for "foo":String>, @level=:Low, @negate=false, @valid=false>
The optional blank?
method is not implemented (unlike in Ruby on Rails, for instance), so the result of the test shows that the spec passed.
Security
As a basic form of security Spectus provides a set of SHA512 checksums for
every Gem release. These checksums can be found in the checksum/
directory.
Although these checksums do not prevent malicious users from tampering with a
built Gem they can be used for basic integrity verification purposes.
The checksum of a file can be checked using the sha512sum
command. For
example:
$ sha512sum pkg/spectus-2.3.0.gem
e9e35e1953104e2d428b0f217e418db3c1baecd9e011b2545f9fcba4ff7e3bba674c6b928b3d8db842a139cd7cc9806d77ebdc7f710ece4f2aecb343703e2451 pkg/spectus-2.3.0.gem
Versioning
Spectus follows Semantic Versioning 2.0.
Contributing
- Fork it
- 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
License
See LICENSE.md
file.