no_moss 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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