rspec-activemodel-mocks 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/rspec/rspec-activemodel-mocks.png?branch=master)](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'
|