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 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in no_moss.gemspec
4
+ gemspec
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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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,2 @@
1
+ require 'no_moss/rspec/matchers'
2
+ require 'no_moss/rspec/shared_examples'
@@ -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
@@ -0,0 +1,5 @@
1
+ shared_examples "plays the role of" do |role|
2
+ it "plays the role of #{role}" do
3
+ expect(subject).to play_role(role)
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module NoMoss
2
+ VERSION = "0.6.0"
3
+ end
data/lib/no_moss.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "no_moss/version"
2
+ require "no_moss/role_api"
3
+
4
+ module NoMoss
5
+ class NoRoleApiMethodError < NoMethodError ; end
6
+
7
+ def self.define_role(&b)
8
+ role = RoleApi.new
9
+ role.module_eval(&b)
10
+ role
11
+ end
12
+ end
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
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'no_moss'
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