no_moss 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +87 -0
- data/Rakefile +6 -0
- data/lib/no_moss/role_api.rb +52 -0
- data/lib/no_moss/rspec/all.rb +2 -0
- data/lib/no_moss/rspec/matchers.rb +14 -0
- data/lib/no_moss/rspec/shared_examples.rb +5 -0
- data/lib/no_moss/version.rb +3 -0
- data/lib/no_moss.rb +12 -0
- data/no_moss.gemspec +31 -0
- data/spec/no_moss/role_api_spec.rb +57 -0
- data/spec/no_moss/rspec_helpers_spec.rb +51 -0
- data/spec/no_moss_spec.rb +18 -0
- data/spec/spec_helper.rb +2 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA512:
|
3
|
+
metadata.gz: 2a94157ef3e84ee08073dc7a69e717ded2cf561a95250560e5eaa2af93a0c31853e734bd5f3260f9c3ff2d848c62ee41da503985f9af01b4cb7e1c04746bbd4f
|
4
|
+
data.tar.gz: 8b6d9415f9819134c6ed5843a24c5f20d42df4dfff2ff8fa25e1395c1b496c5e4bbd2d15d57c4d24d4d6cc65fb9dd8686e51cb29b18344839c0da7d1a06de67d
|
5
|
+
SHA1:
|
6
|
+
metadata.gz: 5531f31dbcf225ffb0004e7ab8187f72b6ae4ab4
|
7
|
+
data.tar.gz: 36e6b2c48291330c1ad32cbba3b3a38ecffd3b3f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 livingsocial
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# no\_moss
|
2
|
+
|
3
|
+
Provides a means of defining object role APIs that should be consistent
|
4
|
+
between test subjects (objects under test) and test doubles (AKA mock
|
5
|
+
objects).
|
6
|
+
|
7
|
+
A role provides data that can be used to verify that a test subject
|
8
|
+
implements the role API. A role also provides a wrapper around a
|
9
|
+
test double that prevents unsupported interactions.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem "no_moss"
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install no_moss
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Basic
|
28
|
+
|
29
|
+
Define a Role API using the NoMoss.define\_role method as follows:
|
30
|
+
|
31
|
+
PersonRole = NoMoss.define_role do
|
32
|
+
implements :id, :name, :email
|
33
|
+
end
|
34
|
+
|
35
|
+
Identify any of a role API's methods not implemented by an object
|
36
|
+
(usually for unit testing purposes) using the role's
|
37
|
+
unimplemented\_methods\_of method as follows:
|
38
|
+
|
39
|
+
PersonRole.unimplemented_methods_of(object)
|
40
|
+
|
41
|
+
Define a Role-restricted proxy to a test subject (object under
|
42
|
+
test) or a test double (AKA mock object) as follows:
|
43
|
+
|
44
|
+
PersonRole.restrict(test_double)
|
45
|
+
|
46
|
+
### Rspec
|
47
|
+
|
48
|
+
Enable no\_moss rspec matchers by adding the following to your
|
49
|
+
spec\_helper file:
|
50
|
+
|
51
|
+
require 'no_moss/rspec/matchers'
|
52
|
+
|
53
|
+
... or for no\_moss rspec matchers and shared examples, ...
|
54
|
+
|
55
|
+
require 'no_moss/rspec/all'
|
56
|
+
|
57
|
+
To verify that an object under test implements a role API...
|
58
|
+
|
59
|
+
it "acts like a person" do
|
60
|
+
expect( someobject ).to play_role( RoleAPIs::Person )
|
61
|
+
end
|
62
|
+
|
63
|
+
To use a shared example to verify that the current subject implements
|
64
|
+
a role API...
|
65
|
+
|
66
|
+
subject{ Fruit }
|
67
|
+
include_examples "plays the role of", RoleAPIs::FruitRepository
|
68
|
+
|
69
|
+
To enforce the role API for an object under test...
|
70
|
+
|
71
|
+
subject{ FruitRepository.restrict(Fruit) }
|
72
|
+
|
73
|
+
To enforce the role API for a test double (AKA mock object) being used
|
74
|
+
as a dependency...
|
75
|
+
|
76
|
+
let(:fruit_repository){ double(:fruit_repository) }
|
77
|
+
subject{ Farm.new(
|
78
|
+
:fruit_repository => RoleAPIs::FruitRepository.restrict(fruit_repository)
|
79
|
+
) }
|
80
|
+
|
81
|
+
## Contributing
|
82
|
+
|
83
|
+
1. Fork it
|
84
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
85
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
86
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module NoMoss
|
4
|
+
class RoleApi < Module
|
5
|
+
def api_methods
|
6
|
+
_api_methods.dup
|
7
|
+
end
|
8
|
+
|
9
|
+
def methods_missing_from(object)
|
10
|
+
_api_methods - object.methods.map(&:to_sym)
|
11
|
+
end
|
12
|
+
|
13
|
+
def restrict(object)
|
14
|
+
proxy_class.new(object)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def implements(*args)
|
20
|
+
_api_methods.concat args
|
21
|
+
end
|
22
|
+
|
23
|
+
def _api_methods
|
24
|
+
@api_methods ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
class AbstractProxy
|
28
|
+
extend Forwardable
|
29
|
+
|
30
|
+
def initialize(object)
|
31
|
+
@object = object
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(name, *args, &b)
|
35
|
+
super
|
36
|
+
rescue NoMethodError
|
37
|
+
raise \
|
38
|
+
NoMoss::NoRoleApiMethodError, \
|
39
|
+
"#{name} method not included in the API of this role."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def proxy_class
|
44
|
+
@proxy_class ||= begin
|
45
|
+
klass = Class.new(AbstractProxy)
|
46
|
+
klass.def_delegators :@object, *api_methods
|
47
|
+
const_set :Proxy, klass
|
48
|
+
klass
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec::Matchers.define :play_role do |role|
|
2
|
+
match do |actual|
|
3
|
+
@methods_missing = role.methods_missing_from(actual)
|
4
|
+
@methods_missing == []
|
5
|
+
end
|
6
|
+
|
7
|
+
failure_message_for_should do |actual|
|
8
|
+
<<-EOF
|
9
|
+
expected #{actual.inspect}
|
10
|
+
to play the role of #{role},
|
11
|
+
but it is missing the following method(s): #{@methods_missing.join(', ')}
|
12
|
+
EOF
|
13
|
+
end
|
14
|
+
end
|
data/lib/no_moss.rb
ADDED
data/no_moss.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'no_moss/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "no_moss"
|
8
|
+
spec.version = NoMoss::VERSION
|
9
|
+
spec.authors = ["Steve Jorgensen"]
|
10
|
+
spec.email = ["steve.jorgensen@livingsocial.com"]
|
11
|
+
spec.description = "Keep test subjects and doubles in sync to avoid false positives"
|
12
|
+
spec.summary = <<-EOS
|
13
|
+
Provides a means of defining object role APIs that should be consistent
|
14
|
+
between test subjects (objects under test) and test doubles (AKA mock
|
15
|
+
objects).
|
16
|
+
A role provides data that can be used to verify that a test subject
|
17
|
+
implements the role API. A role also provides a wrapper around a
|
18
|
+
test double that prevents unsupported interactions.
|
19
|
+
EOS
|
20
|
+
spec.homepage = "http://code.livingsocial.net/sjorgensen"
|
21
|
+
spec.license = "MIT"
|
22
|
+
|
23
|
+
spec.files = `git ls-files`.split($/)
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
spec.add_development_dependency "rspec"
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NoMoss::RoleApi do
|
4
|
+
let(:missile_role) {
|
5
|
+
described_class.new.tap{ |r|
|
6
|
+
r.module_eval do ; implements :target, :launch, :explode ; end
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
10
|
+
describe '.api_methods' do
|
11
|
+
it "returns an array of the API methods of the role" do
|
12
|
+
expect( missile_role.api_methods.sort_by(&:to_s) ).to eq([:explode, :launch, :target])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.methods_missing_from' do
|
17
|
+
let(:object_class) { Class.new.tap { |c|
|
18
|
+
object_methods.each do |m| ; c.send(:define_method, m) { } ; end
|
19
|
+
} }
|
20
|
+
let(:object) { object_class.new }
|
21
|
+
|
22
|
+
describe "given an object that implements all the API methods" do
|
23
|
+
let(:object_methods) { [:arm, :target, :launch, :explode] }
|
24
|
+
|
25
|
+
it "returns an empty array" do
|
26
|
+
expect( missile_role.methods_missing_from(object) ).to eq([])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "given an object that doesn't implement all the API methods" do
|
31
|
+
let(:object_methods) { [:arm, :launch] }
|
32
|
+
|
33
|
+
it "returns an array of unimplemented method names" do
|
34
|
+
expect( missile_role.methods_missing_from(object) ).to eq([:target, :explode])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".restrict" do
|
40
|
+
describe "returned object" do
|
41
|
+
subject { missile_role.restrict(underlying_obj) }
|
42
|
+
let(:underlying_obj) { double(:underlying_obj) }
|
43
|
+
|
44
|
+
it "delegates API method calls to the underlying object, including blocks" do
|
45
|
+
expect(underlying_obj).to receive(:target).with(:earth).and_yield :something
|
46
|
+
yielded = nil
|
47
|
+
subject.target :earth do |v| ; yielded = v ; end
|
48
|
+
expect( yielded ).to eq(:something)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "does not delegate non-API calls" do
|
52
|
+
allow(underlying_obj).to receive(:foo)
|
53
|
+
expect( lambda{ subject.foo } ).to raise_exception(NoMoss::NoRoleApiMethodError)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'no_moss/rspec/all'
|
4
|
+
|
5
|
+
module Agriculture
|
6
|
+
module Roles
|
7
|
+
Cattle = NoMoss.define_role do
|
8
|
+
implements :moo, :low
|
9
|
+
implements :grunt
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Animals
|
14
|
+
class Yak
|
15
|
+
def moo ; "Mooooooooo" ; end
|
16
|
+
def low ; "Mooooooooo" ; end
|
17
|
+
def grunt ; "hmph" ; end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Cat
|
21
|
+
def meow ; "Meow" ; end
|
22
|
+
def purr ; "Purrrrr" ; end
|
23
|
+
def claw ; "*scratch*" ; end
|
24
|
+
def destroy_furniture
|
25
|
+
meow; purr; claw
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
describe 'RSpec helpers' do
|
33
|
+
let(:yak) { Agriculture::Animals::Yak.new }
|
34
|
+
let(:cat) { Agriculture::Animals::Cat.new }
|
35
|
+
|
36
|
+
describe "custom matcher" do
|
37
|
+
it 'should know when an object plays a role' do
|
38
|
+
expect(yak).to play_role(Agriculture::Roles::Cattle)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should know when an object does not play a role' do
|
42
|
+
expect(cat).to_not play_role(Agriculture::Roles::Cattle)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "shared examples" do
|
47
|
+
describe Agriculture::Animals::Yak do
|
48
|
+
include_examples "plays the role of", Agriculture::Roles::Cattle
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NoMoss do
|
4
|
+
it 'should have a version number' do
|
5
|
+
NoMoss::VERSION.should_not be_nil
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.define_role' do
|
9
|
+
it "defines a role that implements specific methods" do
|
10
|
+
role = NoMoss.define_role do
|
11
|
+
implements :foo, :bar
|
12
|
+
implements :baz
|
13
|
+
end
|
14
|
+
|
15
|
+
expect( role.api_methods.sort_by(&:to_s) ).to eq([:bar, :baz, :foo])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: no_moss
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Jorgensen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2013-11-18 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
type: :development
|
16
|
+
name: bundler
|
17
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: "1.3"
|
22
|
+
requirement: *id001
|
23
|
+
prerelease: false
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
type: :development
|
26
|
+
name: rake
|
27
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- &id003
|
30
|
+
- ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: "0"
|
33
|
+
requirement: *id002
|
34
|
+
prerelease: false
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
type: :development
|
37
|
+
name: rspec
|
38
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- *id003
|
41
|
+
requirement: *id004
|
42
|
+
prerelease: false
|
43
|
+
description: Keep test subjects and doubles in sync to avoid false positives
|
44
|
+
email:
|
45
|
+
- steve.jorgensen@livingsocial.com
|
46
|
+
executables: []
|
47
|
+
|
48
|
+
extensions: []
|
49
|
+
|
50
|
+
extra_rdoc_files: []
|
51
|
+
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .rspec
|
55
|
+
- .travis.yml
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- lib/no_moss.rb
|
61
|
+
- lib/no_moss/role_api.rb
|
62
|
+
- lib/no_moss/rspec/all.rb
|
63
|
+
- lib/no_moss/rspec/matchers.rb
|
64
|
+
- lib/no_moss/rspec/shared_examples.rb
|
65
|
+
- lib/no_moss/version.rb
|
66
|
+
- no_moss.gemspec
|
67
|
+
- spec/no_moss/role_api_spec.rb
|
68
|
+
- spec/no_moss/rspec_helpers_spec.rb
|
69
|
+
- spec/no_moss_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
homepage: http://code.livingsocial.net/sjorgensen
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- *id003
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- *id003
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.0.13
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: Provides a means of defining object role APIs that should be consistent between test subjects (objects under test) and test doubles (AKA mock objects). A role provides data that can be used to verify that a test subject implements the role API. A role also provides a wrapper around a test double that prevents unsupported interactions.
|
94
|
+
test_files:
|
95
|
+
- spec/no_moss/role_api_spec.rb
|
96
|
+
- spec/no_moss/rspec_helpers_spec.rb
|
97
|
+
- spec/no_moss_spec.rb
|
98
|
+
- spec/spec_helper.rb
|