rspec-activemodel-mocks 1.0.0.beta1
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/.yardopts +4 -0
- data/License.txt +23 -0
- data/README.md +70 -0
- data/features/mocks/mock_model.feature +147 -0
- data/features/mocks/stub_model.feature +58 -0
- data/features/step_definitions/additional_cli_steps.rb +4 -0
- data/features/step_definitions/model_steps.rb +3 -0
- data/features/support/env.rb +53 -0
- data/features/support/rubinius.rb +6 -0
- data/lib/rspec/active_model/mocks/mocks.rb +271 -0
- data/lib/rspec/active_model/mocks/version.rb +9 -0
- data/lib/rspec/active_model/mocks.rb +8 -0
- data/lib/rspec-activemodel-mocks.rb +1 -0
- data/spec/rspec/active_model/mocks/mock_model_spec.rb +435 -0
- data/spec/rspec/active_model/mocks/stub_model_spec.rb +154 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/ar_classes.rb +42 -0
- data/spec/support/helpers.rb +34 -0
- data/spec/support/matchers.rb +9 -0
- data/spec/support/minitest_support.rb +12 -0
- data/spec/support/null_object.rb +6 -0
- metadata +194 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5a5cc1ea66ca12596ba1723b13be6ada5fbcc6bc
|
4
|
+
data.tar.gz: 9bad54a7e796d1465ecf0922b6d11d32e74c0b80
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 17a71ff3a6b9b8ce1ef5878966b11ad2cf549499cb09aeb02d1ae3eea0800fc4ba5de63365298d0406e7c86a273d78f52cabb53de84ad72f70ebcc09dd3de5f1
|
7
|
+
data.tar.gz: 94d431c9b32d5fea92dfdb428d3156dfc526a8e22ca6d489c0225a3f3fb7224c25fa9c93d1fc51838fc2c6f195e0154ba1d93b5d5177ba48873b19937fab4015
|
data/.yardopts
ADDED
data/License.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2012 David Chelimsky, Andy Lindeman
|
4
|
+
Copyright (c) 2006 David Chelimsky, The RSpec Development Team
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
20
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
21
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# RSpec::ActiveModel::Mocks [](http://travis-ci.org/rspec/rspec-activemodel-mocks)
|
2
|
+
|
3
|
+
RSpec::ActiveModel::Mocks provides tools for testing `ActiveModel` classes.
|
4
|
+
|
5
|
+
mock_model(Person, name: "Fred")
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
Add this line to your application's gemfile:
|
10
|
+
|
11
|
+
gem 'rspec-activemodel-mocks'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
If you are using [rspec-rails](https://github.com/rspec/rspec-rails) and have
|
18
|
+
followed the installation instructions there, you're all set to use `stub_model`
|
19
|
+
and `mock_model`.
|
20
|
+
|
21
|
+
To use `stub_model` and `mock_model` without rspec-rails, require the
|
22
|
+
following file:
|
23
|
+
|
24
|
+
require 'rspec/active_model/mocks'
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Mock
|
29
|
+
|
30
|
+
Creates a test double representing `string_or_model_class` with common
|
31
|
+
ActiveModel methods stubbed out. Additional methods may be easily stubbed
|
32
|
+
(via `add_stubs`) if `stubs` is passed. This is most useful for impersonating
|
33
|
+
models that don't exist yet.
|
34
|
+
|
35
|
+
ActiveModel methods, plus `new_record?`, are stubbed out implicitly.
|
36
|
+
`new_record?` returns the inverse of `persisted?`, and is present only for
|
37
|
+
compatibility with extension frameworks that have yet to update themselves to
|
38
|
+
the ActiveModel API (which declares `persisted?`, not `new_record?`).
|
39
|
+
|
40
|
+
`string_or_model_class` can be any of:
|
41
|
+
|
42
|
+
* A String representing a Class that does not exist
|
43
|
+
* A String representing a Class that extends `ActiveModel::Naming`
|
44
|
+
* A Class that extends `ActiveModel::Naming`
|
45
|
+
|
46
|
+
### Stub
|
47
|
+
|
48
|
+
Creates an instance of `Model` with `to_param` stubbed using a generated value
|
49
|
+
that is unique to each object. If `Model` is an `ActiveRecord` model, it is
|
50
|
+
prohibited from accessing the database.
|
51
|
+
|
52
|
+
For each key in `stubs`, if the model has a matching attribute (determined by
|
53
|
+
`respond_to?`) it is simply assigned the submitted values. If the model does
|
54
|
+
not have a matching attribute, the key/value pair is assigned as a stub return
|
55
|
+
value using RSpec's mocking/stubbing framework.
|
56
|
+
|
57
|
+
`persisted?` is overridden to return the result of `!id.nil?` This means that
|
58
|
+
by default `persisted?` will return true. If you want the object to behave as a
|
59
|
+
new record, sending it `as_new_record` will set the id to nil. You can also
|
60
|
+
explicitly set `:id => nil`, in which case `persisted?` will return false, but
|
61
|
+
using `as_new_record` makes the example a bit more descriptive.
|
62
|
+
|
63
|
+
While you can use `stub_model` in any example (model, view, controller,
|
64
|
+
helper), it is especially useful in view examples, which are inherently more
|
65
|
+
state-based than interaction-based.
|
66
|
+
|
67
|
+
stub_model(Person)
|
68
|
+
stub_model(Person).as_new_record
|
69
|
+
stub_model(Person, :to_param => 37)
|
70
|
+
stub_model(Person) {|person| person.first_name = "David"}
|
@@ -0,0 +1,147 @@
|
|
1
|
+
Feature: mock_model
|
2
|
+
|
3
|
+
The `mock_model` method generates a test double that acts like an instance of
|
4
|
+
`ActiveModel`. This is different from the `stub_model` method which generates
|
5
|
+
an instance of a real model class.
|
6
|
+
|
7
|
+
The benefit of `mock_model` over `stub_model` is that it is a true double, so
|
8
|
+
the examples are not dependent on the behavior (or mis-behavior), or even the
|
9
|
+
existence of any other code. If you're working on a controller spec and you
|
10
|
+
need a model that doesn't exist, you can pass `mock_model` a string and the
|
11
|
+
generated object will act as though its an instance of the class named by
|
12
|
+
that string.
|
13
|
+
|
14
|
+
Scenario: passing a string that represents a non-existent constant
|
15
|
+
Given a file named "spec/models/car_spec.rb" with:
|
16
|
+
"""ruby
|
17
|
+
require "spec_helper"
|
18
|
+
|
19
|
+
describe "mock_model('Car') with no Car constant in existence" do
|
20
|
+
it "generates a constant" do
|
21
|
+
expect(Object.const_defined?(:Car)).to be_falsey
|
22
|
+
mock_model("Car")
|
23
|
+
expect(Object.const_defined?(:Car)).to be_truthy
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "generates an object that ..." do
|
27
|
+
it "returns the correct name" do
|
28
|
+
car = mock_model("Car")
|
29
|
+
expect(car.class.name).to eq("Car")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "says it is a Car" do
|
33
|
+
car = mock_model("Car")
|
34
|
+
expect(car).to be_a(Car)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
"""
|
39
|
+
When I run `rspec spec/models/car_spec.rb`
|
40
|
+
Then the examples should all pass
|
41
|
+
|
42
|
+
Scenario: passing a string that represents an existing constant
|
43
|
+
Given a file named "spec/models/widget_spec.rb" with:
|
44
|
+
"""ruby
|
45
|
+
require "spec_helper"
|
46
|
+
|
47
|
+
describe Widget do
|
48
|
+
it "uses the existing constant" do
|
49
|
+
widget = mock_model("Widget")
|
50
|
+
expect(widget).to be_a(Widget)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
"""
|
54
|
+
When I run `rspec spec/models/widget_spec.rb`
|
55
|
+
Then the examples should all pass
|
56
|
+
|
57
|
+
Scenario: passing a class that does not extend ActiveModel::Naming
|
58
|
+
Given a file named "spec/models/string_spec.rb" with:
|
59
|
+
"""ruby
|
60
|
+
require "spec_helper"
|
61
|
+
|
62
|
+
describe String do
|
63
|
+
it "raises" do
|
64
|
+
expect { mock_model(String) }.to raise_exception
|
65
|
+
end
|
66
|
+
end
|
67
|
+
"""
|
68
|
+
When I run `rspec spec/models/string_spec.rb`
|
69
|
+
Then the examples should all pass
|
70
|
+
|
71
|
+
Scenario: passing an Active Record constant
|
72
|
+
Given a file named "spec/models/widget_spec.rb" with:
|
73
|
+
"""ruby
|
74
|
+
require "spec_helper"
|
75
|
+
|
76
|
+
describe Widget do
|
77
|
+
let(:widget) { mock_model(Widget) }
|
78
|
+
|
79
|
+
it "is valid by default" do
|
80
|
+
expect(widget).to be_valid
|
81
|
+
end
|
82
|
+
|
83
|
+
it "is not a new record by default" do
|
84
|
+
expect(widget).not_to be_new_record
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can be converted to a new record" do
|
88
|
+
expect(widget.as_new_record).to be_new_record
|
89
|
+
end
|
90
|
+
|
91
|
+
it "sets :id to nil upon destroy" do
|
92
|
+
widget.destroy
|
93
|
+
expect(widget.id).to be_nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
"""
|
97
|
+
When I run `rspec spec/models/widget_spec.rb`
|
98
|
+
Then the examples should all pass
|
99
|
+
|
100
|
+
Scenario: passing an Active Record constant with method stubs
|
101
|
+
Given a file named "spec/models/widget_spec.rb" with:
|
102
|
+
"""ruby
|
103
|
+
require "spec_helper"
|
104
|
+
|
105
|
+
describe "mock_model(Widget) with stubs" do
|
106
|
+
let(:widget) do
|
107
|
+
mock_model Widget, :foo => "bar",
|
108
|
+
:save => true,
|
109
|
+
:update_attributes => false
|
110
|
+
end
|
111
|
+
|
112
|
+
it "supports stubs for methods that don't exist in ActiveModel or ActiveRecord" do
|
113
|
+
expect(widget.foo).to eq("bar")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "supports stubs for methods that do exist" do
|
117
|
+
expect(widget.save).to eq(true)
|
118
|
+
expect(widget.update_attributes).to be_falsey
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#errors" do
|
122
|
+
context "with update_attributes => false" do
|
123
|
+
it "is not empty" do
|
124
|
+
expect(widget.errors).not_to be_empty
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
"""
|
130
|
+
When I run `rspec spec/models/widget_spec.rb`
|
131
|
+
Then the examples should all pass
|
132
|
+
|
133
|
+
Scenario: mock_model outside rails
|
134
|
+
Given a file named "mock_model_outside_rails_spec.rb" with:
|
135
|
+
"""ruby
|
136
|
+
require 'rspec/active_model/mocks'
|
137
|
+
|
138
|
+
describe "Foo" do
|
139
|
+
it "is mockable" do
|
140
|
+
foo = mock_model("Foo")
|
141
|
+
expect(foo.id).to eq(1001)
|
142
|
+
expect(foo.to_param).to eq("1001")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
"""
|
146
|
+
When I run `rspec mock_model_outside_rails_spec.rb`
|
147
|
+
Then the examples should all pass
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Feature: stub_model
|
2
|
+
|
3
|
+
The stub_model method generates an instance of a Active Model model.
|
4
|
+
|
5
|
+
While you can use stub_model in any example (model, view, controller,
|
6
|
+
helper), it is especially useful in view examples, which are inherently
|
7
|
+
more state-based than interaction-based.
|
8
|
+
|
9
|
+
Scenario: passing an Active Record constant with a hash of stubs
|
10
|
+
Given a file named "spec/models/widget_spec.rb" with:
|
11
|
+
"""ruby
|
12
|
+
require "spec_helper"
|
13
|
+
|
14
|
+
describe "stub_model(Widget) with a hash of stubs" do
|
15
|
+
let(:widget) do
|
16
|
+
stub_model Widget, :id => 5, :random_attribute => true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "stubs :id" do
|
20
|
+
expect(widget.id).to eql(5)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "stubs :random_attribute" do
|
24
|
+
expect(widget.random_attribute).to be_truthy
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns false for new_record? if :id is set" do
|
28
|
+
expect(widget).not_to be_new_record
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can be converted to a new record" do
|
32
|
+
widget.as_new_record
|
33
|
+
expect(widget).to be_new_record
|
34
|
+
end
|
35
|
+
end
|
36
|
+
"""
|
37
|
+
When I run `rspec spec/models/widget_spec.rb`
|
38
|
+
Then the examples should all pass
|
39
|
+
|
40
|
+
Scenario: passing an Active Record constant with a block of stubs
|
41
|
+
Given a file named "spec/models/widget_spec.rb" with:
|
42
|
+
"""ruby
|
43
|
+
require "spec_helper"
|
44
|
+
|
45
|
+
describe "stub_model(Widget) with a block of stubs" do
|
46
|
+
let(:widget) do
|
47
|
+
stub_model Widget do |widget|
|
48
|
+
widget.id = 5
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "stubs :id" do
|
53
|
+
expect(widget.id).to eql(5)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
"""
|
57
|
+
When I run `rspec spec/models/widget_spec.rb`
|
58
|
+
Then the examples should all pass
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
|
3
|
+
module ArubaExt
|
4
|
+
def run(cmd)
|
5
|
+
super(cmd =~ /^rspec/ ? "bin/#{cmd}" : cmd)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
World(ArubaExt)
|
10
|
+
|
11
|
+
Before do
|
12
|
+
@aruba_timeout_seconds = 30
|
13
|
+
end
|
14
|
+
|
15
|
+
unless File.directory?('./tmp/sample')
|
16
|
+
system "rake generate:sample"
|
17
|
+
end
|
18
|
+
|
19
|
+
def aruba_path(file_or_dir)
|
20
|
+
File.expand_path("../../../#{file_or_dir.sub('sample','aruba')}", __FILE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sample_path(file_or_dir)
|
24
|
+
File.expand_path("../../../#{file_or_dir}", __FILE__)
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_symlink(file_or_dir)
|
28
|
+
source = sample_path(file_or_dir)
|
29
|
+
target = aruba_path(file_or_dir)
|
30
|
+
system "ln -s #{source} #{target}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy(file_or_dir)
|
34
|
+
source = sample_path(file_or_dir)
|
35
|
+
target = aruba_path(file_or_dir)
|
36
|
+
system "cp -r #{source} #{target}"
|
37
|
+
end
|
38
|
+
|
39
|
+
Before do
|
40
|
+
steps %Q{
|
41
|
+
Given a directory named "spec"
|
42
|
+
}
|
43
|
+
|
44
|
+
Dir['tmp/sample/*'].each do |file_or_dir|
|
45
|
+
if !(file_or_dir =~ /spec$/)
|
46
|
+
write_symlink(file_or_dir)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
["spec/spec_helper.rb"].each do |file_or_dir|
|
51
|
+
write_symlink("tmp/sample/#{file_or_dir}")
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/object/to_param'
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module RSpec::ActiveModel::Mocks
|
6
|
+
class IllegalDataAccessException < StandardError; end
|
7
|
+
module Mocks
|
8
|
+
|
9
|
+
module ActiveModelInstanceMethods
|
10
|
+
# Stubs `persisted?` to return false and `id` to return nil
|
11
|
+
# @return self
|
12
|
+
def as_new_record
|
13
|
+
RSpec::Mocks.allow_message(self, :persisted?).and_return(false)
|
14
|
+
RSpec::Mocks.allow_message(self, :id).and_return(nil)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns true by default. Override with a stub.
|
19
|
+
def persisted?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns false for names matching <tt>/_before_type_cast$/</tt>,
|
24
|
+
# otherwise delegates to super.
|
25
|
+
def respond_to?(message, include_private=false)
|
26
|
+
message.to_s =~ /_before_type_cast$/ ? false : super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Starting with Rails 4.1, ActiveRecord associations are inversible
|
31
|
+
# by default. This class represents an association from the mocked
|
32
|
+
# model's perspective.
|
33
|
+
#
|
34
|
+
# @private
|
35
|
+
class Association
|
36
|
+
attr_accessor :target, :inversed
|
37
|
+
|
38
|
+
def initialize(association_name)
|
39
|
+
@association_name = association_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ActiveRecordInstanceMethods
|
44
|
+
# Stubs `persisted?` to return `false` and `id` to return `nil`.
|
45
|
+
def destroy
|
46
|
+
RSpec::Mocks.allow_message(self, :persisted?).and_return(false)
|
47
|
+
RSpec::Mocks.allow_message(self, :id).and_return(nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Transforms the key to a method and calls it.
|
51
|
+
def [](key)
|
52
|
+
send(key)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the opposite of `persisted?`
|
56
|
+
def new_record?
|
57
|
+
!persisted?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an object representing an association from the mocked
|
61
|
+
# model's perspective. For use by Rails internally only.
|
62
|
+
def association(association_name)
|
63
|
+
@associations ||= Hash.new { |h, k| h[k] = Association.new(k) }
|
64
|
+
@associations[association_name]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates a test double representing `string_or_model_class` with common
|
69
|
+
# ActiveModel methods stubbed out. Additional methods may be easily
|
70
|
+
# stubbed (via add_stubs) if `stubs` is passed. This is most useful for
|
71
|
+
# impersonating models that don't exist yet.
|
72
|
+
#
|
73
|
+
# ActiveModel methods, plus <tt>new_record?</tt>, are
|
74
|
+
# stubbed out implicitly. <tt>new_record?</tt> returns the inverse of
|
75
|
+
# <tt>persisted?</tt>, and is present only for compatibility with
|
76
|
+
# extension frameworks that have yet to update themselves to the
|
77
|
+
# ActiveModel API (which declares <tt>persisted?</tt>, not
|
78
|
+
# <tt>new_record?</tt>).
|
79
|
+
#
|
80
|
+
# `string_or_model_class` can be any of:
|
81
|
+
#
|
82
|
+
# * A String representing a Class that does not exist
|
83
|
+
# * A String representing a Class that extends ActiveModel::Naming
|
84
|
+
# * A Class that extends ActiveModel::Naming
|
85
|
+
def mock_model(string_or_model_class, stubs = {})
|
86
|
+
if String === string_or_model_class
|
87
|
+
if Object.const_defined?(string_or_model_class)
|
88
|
+
model_class = Object.const_get(string_or_model_class)
|
89
|
+
else
|
90
|
+
model_class = Object.const_set(string_or_model_class, Class.new do
|
91
|
+
extend ::ActiveModel::Naming
|
92
|
+
def self.primary_key; :id; end
|
93
|
+
end)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
model_class = string_or_model_class
|
97
|
+
end
|
98
|
+
|
99
|
+
unless model_class.kind_of? ::ActiveModel::Naming
|
100
|
+
raise ArgumentError.new <<-EOM
|
101
|
+
The mock_model method can only accept as its first argument:
|
102
|
+
* A String representing a Class that does not exist
|
103
|
+
* A String representing a Class that extends ActiveModel::Naming
|
104
|
+
* A Class that extends ActiveModel::Naming
|
105
|
+
|
106
|
+
It received #{model_class.inspect}
|
107
|
+
EOM
|
108
|
+
end
|
109
|
+
|
110
|
+
stubs = {:id => next_id}.merge(stubs)
|
111
|
+
stubs = {:persisted? => !!stubs[:id],
|
112
|
+
:destroyed? => false,
|
113
|
+
:marked_for_destruction? => false,
|
114
|
+
:valid? => true,
|
115
|
+
:blank? => false}.merge(stubs)
|
116
|
+
|
117
|
+
double("#{model_class.name}_#{stubs[:id]}", stubs).tap do |m|
|
118
|
+
msingleton = class << m; self; end
|
119
|
+
msingleton.class_eval do
|
120
|
+
include ActiveModelInstanceMethods
|
121
|
+
include ActiveRecordInstanceMethods if defined?(ActiveRecord)
|
122
|
+
include ActiveModel::Conversion
|
123
|
+
include ActiveModel::Validations
|
124
|
+
end
|
125
|
+
if defined?(ActiveRecord)
|
126
|
+
[:save, :update_attributes, :update].each do |key|
|
127
|
+
if stubs[key] == false
|
128
|
+
RSpec::Mocks.allow_message(m.errors, :empty?).and_return(false)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
msingleton.__send__(:define_method, :is_a?) do |other|
|
134
|
+
model_class.ancestors.include?(other)
|
135
|
+
end unless stubs.has_key?(:is_a?)
|
136
|
+
|
137
|
+
msingleton.__send__(:define_method, :kind_of?) do |other|
|
138
|
+
model_class.ancestors.include?(other)
|
139
|
+
end unless stubs.has_key?(:kind_of?)
|
140
|
+
|
141
|
+
msingleton.__send__(:define_method, :instance_of?) do |other|
|
142
|
+
other == model_class
|
143
|
+
end unless stubs.has_key?(:instance_of?)
|
144
|
+
|
145
|
+
msingleton.__send__(:define_method, :__model_class_has_column?) do |method_name|
|
146
|
+
model_class.respond_to?(:column_names) && model_class.column_names.include?(method_name.to_s)
|
147
|
+
end
|
148
|
+
|
149
|
+
msingleton.__send__(:define_method, :has_attribute?) do |attr_name|
|
150
|
+
__model_class_has_column?(attr_name)
|
151
|
+
end unless stubs.has_key?(:has_attribute?)
|
152
|
+
|
153
|
+
msingleton.__send__(:define_method, :respond_to?) do |method_name, *args|
|
154
|
+
include_private = args.first || false
|
155
|
+
__model_class_has_column?(method_name) ? true : super(method_name, include_private)
|
156
|
+
end unless stubs.has_key?(:respond_to?)
|
157
|
+
|
158
|
+
msingleton.__send__(:define_method, :method_missing) do |m, *a, &b|
|
159
|
+
respond_to?(m) ? null_object? ? self : nil : super(m, *a, &b)
|
160
|
+
end
|
161
|
+
|
162
|
+
msingleton.__send__(:define_method, :class) do
|
163
|
+
model_class
|
164
|
+
end unless stubs.has_key?(:class)
|
165
|
+
|
166
|
+
mock_param = to_param
|
167
|
+
msingleton.__send__(:define_method, :to_s) do
|
168
|
+
"#{model_class.name}_#{mock_param}"
|
169
|
+
end unless stubs.has_key?(:to_s)
|
170
|
+
yield m if block_given?
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module ActiveModelStubExtensions
|
175
|
+
# Stubs `persisted` to return false and `id` to return nil
|
176
|
+
def as_new_record
|
177
|
+
RSpec::Mocks.allow_message(self, :persisted?).and_return(false)
|
178
|
+
RSpec::Mocks.allow_message(self, :id).and_return(nil)
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns `true` by default. Override with a stub.
|
183
|
+
def persisted?
|
184
|
+
true
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
module ActiveRecordStubExtensions
|
189
|
+
# Stubs `id` (or other primary key method) to return nil
|
190
|
+
def as_new_record
|
191
|
+
self.__send__("#{self.class.primary_key}=", nil)
|
192
|
+
super
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns the opposite of `persisted?`.
|
196
|
+
def new_record?
|
197
|
+
!persisted?
|
198
|
+
end
|
199
|
+
|
200
|
+
# Raises an IllegalDataAccessException (stubbed models are not allowed to access the database)
|
201
|
+
# @raises IllegalDataAccessException
|
202
|
+
def connection
|
203
|
+
raise RSpec::ActiveModel::Mocks::IllegalDataAccessException.new("stubbed models are not allowed to access the database")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Creates an instance of `Model` with `to_param` stubbed using a
|
208
|
+
# generated value that is unique to each object. If `Model` is an
|
209
|
+
# `ActiveRecord` model, it is prohibited from accessing the database.
|
210
|
+
#
|
211
|
+
# For each key in `stubs`, if the model has a matching attribute
|
212
|
+
# (determined by `respond_to?`) it is simply assigned the submitted values.
|
213
|
+
# If the model does not have a matching attribute, the key/value pair is
|
214
|
+
# assigned as a stub return value using RSpec's mocking/stubbing
|
215
|
+
# framework.
|
216
|
+
#
|
217
|
+
# <tt>persisted?</tt> is overridden to return the result of !id.nil?
|
218
|
+
# This means that by default persisted? will return true. If you want
|
219
|
+
# the object to behave as a new record, sending it `as_new_record` will
|
220
|
+
# set the id to nil. You can also explicitly set :id => nil, in which
|
221
|
+
# case persisted? will return false, but using `as_new_record` makes the
|
222
|
+
# example a bit more descriptive.
|
223
|
+
#
|
224
|
+
# While you can use stub_model in any example (model, view, controller,
|
225
|
+
# helper), it is especially useful in view examples, which are
|
226
|
+
# inherently more state-based than interaction-based.
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
#
|
230
|
+
# stub_model(Person)
|
231
|
+
# stub_model(Person).as_new_record
|
232
|
+
# stub_model(Person, :to_param => 37)
|
233
|
+
# stub_model(Person) {|person| person.first_name = "David"}
|
234
|
+
def stub_model(model_class, stubs={})
|
235
|
+
model_class.new.tap do |m|
|
236
|
+
m.extend ActiveModelStubExtensions
|
237
|
+
if defined?(ActiveRecord) && model_class < ActiveRecord::Base
|
238
|
+
m.extend ActiveRecordStubExtensions
|
239
|
+
primary_key = model_class.primary_key.to_sym
|
240
|
+
stubs = {primary_key => next_id}.merge(stubs)
|
241
|
+
stubs = {:persisted? => !!stubs[primary_key]}.merge(stubs)
|
242
|
+
else
|
243
|
+
stubs = {:id => next_id}.merge(stubs)
|
244
|
+
stubs = {:persisted? => !!stubs[:id]}.merge(stubs)
|
245
|
+
end
|
246
|
+
stubs = {:blank? => false}.merge(stubs)
|
247
|
+
|
248
|
+
stubs.each do |message, return_value|
|
249
|
+
if m.respond_to?("#{message}=")
|
250
|
+
m.__send__("#{message}=", return_value)
|
251
|
+
else
|
252
|
+
RSpec::Mocks.allow_message(m, message).and_return(return_value)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
yield m if block_given?
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
@@model_id = 1000
|
263
|
+
|
264
|
+
def next_id
|
265
|
+
@@model_id += 1
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
RSpec.configuration.include RSpec::ActiveModel::Mocks::Mocks
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rspec/active_model/mocks'
|