remockable 0.1.1 → 0.1.2

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.
Files changed (38) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +1 -1
  6. data/README.markdown +50 -36
  7. data/Rakefile +1 -0
  8. data/lib/remockable/active_model/helpers.rb +2 -0
  9. data/lib/remockable/active_record/helpers.rb +2 -0
  10. data/lib/remockable/version.rb +1 -1
  11. data/remockable.gemspec +23 -0
  12. data/spec/active_model/allow_mass_assignment_of_spec.rb +86 -0
  13. data/spec/active_model/allow_values_for_spec.rb +46 -0
  14. data/spec/active_model/validate_acceptance_of_spec.rb +16 -0
  15. data/spec/active_model/validate_confirmation_of_spec.rb +14 -0
  16. data/spec/active_model/validate_exclusion_of_spec.rb +16 -0
  17. data/spec/active_model/validate_format_of_spec.rb +18 -0
  18. data/spec/active_model/validate_inclusion_of_spec.rb +16 -0
  19. data/spec/active_model/validate_length_of_spec.rb +26 -0
  20. data/spec/active_model/validate_numericality_of_spec.rb +23 -0
  21. data/spec/active_model/validate_presence_of_spec.rb +14 -0
  22. data/spec/active_record/accept_nested_attributes_for_spec.rb +47 -0
  23. data/spec/active_record/belong_to_spec.rb +62 -0
  24. data/spec/active_record/have_and_belong_to_many_spec.rb +67 -0
  25. data/spec/active_record/have_column_spec.rb +109 -0
  26. data/spec/active_record/have_default_scope_spec.rb +169 -0
  27. data/spec/active_record/have_index_spec.rb +89 -0
  28. data/spec/active_record/have_many_spec.rb +70 -0
  29. data/spec/active_record/have_one_spec.rb +61 -0
  30. data/spec/active_record/have_scope_spec.rb +229 -0
  31. data/spec/active_record/validate_associated_spec.rb +27 -0
  32. data/spec/active_record/validate_uniqueness_of_spec.rb +23 -0
  33. data/spec/spec_helper.rb +17 -0
  34. data/spec/support/active_record_example_group.rb +22 -0
  35. data/spec/support/class_builder.rb +39 -0
  36. data/spec/support/shared/a_validation_matcher.rb +130 -0
  37. data/spec/support/shared/an_active_record_matcher.rb +53 -0
  38. metadata +134 -40
@@ -0,0 +1,229 @@
1
+ require 'spec_helper'
2
+
3
+ describe :have_scope do
4
+ let(:options) { :one }
5
+
6
+ it_behaves_like 'an Active Record matcher' do
7
+ let(:model) { build_class(:User, ActiveRecord::Base) }
8
+
9
+ before do
10
+ create_table(:users) { |table| table.string(:one) }
11
+ end
12
+
13
+ subject { model.new }
14
+
15
+ context 'description' do
16
+ let(:matcher) { send(matcher_name, *options) }
17
+
18
+ it 'has a custom description' do
19
+ name = matcher.instance_variable_get(:@name).to_s.gsub(/_/, ' ')
20
+ with = " with #{matcher.expected}" if matcher.expected.any?
21
+
22
+ matcher.description.should == "have scope #{name}#{with}"
23
+ end
24
+ end
25
+
26
+ context 'with a scope name' do
27
+ it 'matches if the scope exists' do
28
+ model.instance_eval { scope(:no_one, where(:one => nil)) }
29
+ model.should have_scope(:no_one)
30
+ end
31
+
32
+ it 'does not match if the scope does not exist' do
33
+ model.should_not have_scope(:no_one)
34
+ end
35
+ end
36
+
37
+ context 'with an eager_load value' do
38
+ it 'matches if the scope exists and the query matches' do
39
+ model.instance_eval { scope(:loaded, eager_load(:order)) }
40
+ model.should have_scope(:loaded).eager_load(:order)
41
+ end
42
+
43
+ it 'does not match if the query does not match' do
44
+ model.instance_eval { scope(:loaded, eager_load(:order)) }
45
+ model.should_not have_scope(:loaded).eager_load(:from)
46
+ end
47
+ end
48
+
49
+ context 'with a from value' do
50
+ it 'matches if the scope exists and the query matches' do
51
+ model.instance_eval { scope(:used, from('users')) }
52
+ model.should have_scope(:used).from('users')
53
+ end
54
+
55
+ it 'does not match if the query does not match' do
56
+ model.instance_eval { scope(:used, from('users')) }
57
+ model.should_not have_scope(:used).from('friends')
58
+ end
59
+ end
60
+
61
+ context 'with a group value' do
62
+ it 'matches if the scope exists and the query matches' do
63
+ model.instance_eval { scope(:grouped, group('one')) }
64
+ model.should have_scope(:grouped).group('one')
65
+ end
66
+
67
+ it 'does not match if the query does not match' do
68
+ model.instance_eval { scope(:grouped, group('one')) }
69
+ model.should_not have_scope(:grouped).group('two')
70
+ end
71
+ end
72
+
73
+ context 'with a having value' do
74
+ it 'matches if the scope exists and the query matches' do
75
+ model.instance_eval { scope(:had, having('COUNT(*) > 1')) }
76
+ model.should have_scope(:had).having('COUNT(*) > 1')
77
+ end
78
+
79
+ it 'does not match if the query does not match' do
80
+ model.instance_eval { scope(:had, having('COUNT(*) > 1')) }
81
+ model.should_not have_scope(:had).having('COUNT(*) > 2')
82
+ end
83
+ end
84
+
85
+ context 'with an includes value' do
86
+ it 'matches if the scope exists and the query matches' do
87
+ model.instance_eval { scope(:joined, includes(:company)) }
88
+ model.should have_scope(:joined).includes(:company)
89
+ end
90
+
91
+ it 'does not match if the query does not match' do
92
+ model.instance_eval { scope(:joined, includes(:company)) }
93
+ model.should_not have_scope(:joined).includes(:address)
94
+ end
95
+ end
96
+
97
+ context 'with a joins value' do
98
+ it 'matches if the scope exists and the query matches' do
99
+ model.instance_eval { scope(:joined, joins('INNER JOIN friends')) }
100
+ model.should have_scope(:joined).joins('INNER JOIN friends')
101
+ end
102
+
103
+ it 'does not match if the query does not match' do
104
+ model.instance_eval { scope(:joined, joins('INNER JOIN friends')) }
105
+ model.should_not have_scope(:joined).joins('INNER JOIN enemies')
106
+ end
107
+ end
108
+
109
+ context 'with a limit value' do
110
+ it 'matches if the scope exists and the query matches' do
111
+ model.instance_eval { scope(:limited, limit(10)) }
112
+ model.should have_scope(:limited).limit(10)
113
+ end
114
+
115
+ it 'does not match if the query does not match' do
116
+ model.instance_eval { scope(:limited, limit(10)) }
117
+ model.should_not have_scope(:limited).limit(15)
118
+ end
119
+ end
120
+
121
+ context 'with a lock value' do
122
+ it 'matches if the scope exists and the query matches' do
123
+ model.instance_eval { scope(:locked, lock(true)) }
124
+ model.should have_scope(:locked).lock(true)
125
+ end
126
+
127
+ it 'does not match if the query does not match' do
128
+ model.instance_eval { scope(:locked, lock(true)) }
129
+ model.should_not have_scope(:locked).lock(false)
130
+ end
131
+ end
132
+
133
+ context 'with an offset value' do
134
+ it 'matches if the scope exists and the query matches' do
135
+ model.instance_eval { scope(:shifted, offset(10)) }
136
+ model.should have_scope(:shifted).offset(10)
137
+ end
138
+
139
+ it 'does not match if the query does not match' do
140
+ model.instance_eval { scope(:shifted, offset(10)) }
141
+ model.should_not have_scope(:shifted).offset(15)
142
+ end
143
+ end
144
+
145
+ context 'with an order value' do
146
+ it 'matches if the scope exists and the query matches' do
147
+ model.instance_eval { scope(:ordered, order('one')) }
148
+ model.should have_scope(:ordered).order('one')
149
+ end
150
+
151
+ it 'does not match if the query does not match' do
152
+ model.instance_eval { scope(:ordered, order('one')) }
153
+ model.should_not have_scope(:ordered).order('id')
154
+ end
155
+ end
156
+
157
+ context 'with a preload value' do
158
+ it 'matches if the scope exists and the query matches' do
159
+ model.instance_eval { scope(:preloaded, preload(:company)) }
160
+ model.should have_scope(:preloaded).preload(:company)
161
+ end
162
+
163
+ it 'does not match if the query does not match' do
164
+ model.instance_eval { scope(:preloaded, preload(:company)) }
165
+ model.should_not have_scope(:preloaded).preload(:address)
166
+ end
167
+ end
168
+
169
+ context 'with a readonly value' do
170
+ it 'matches if the scope exists and the query matches' do
171
+ model.instance_eval { scope(:prepared, readonly(false)) }
172
+ model.should have_scope(:prepared).readonly(false)
173
+ end
174
+
175
+ it 'does not match if the query does not match' do
176
+ model.instance_eval { scope(:prepared, readonly(false)) }
177
+ model.should_not have_scope(:prepared).readonly(true)
178
+ end
179
+ end
180
+
181
+ context 'with a reorder value' do
182
+ it 'matches if the scope exists and the query matches' do
183
+ model.instance_eval { scope(:reordered, reorder('one')) }
184
+ model.should have_scope(:reordered).reorder('one')
185
+ end
186
+
187
+ it 'does not match if the query does not match' do
188
+ model.instance_eval { scope(:reordered, reorder('one')) }
189
+ model.should_not have_scope(:reordered).reorder('id')
190
+ end
191
+ end
192
+
193
+ context 'with a select value' do
194
+ it 'matches if the scope exists and the query matches' do
195
+ model.instance_eval { scope(:ones, select('one')) }
196
+ model.should have_scope(:ones).select('one')
197
+ end
198
+
199
+ it 'does not match if the query does not match' do
200
+ model.instance_eval { scope(:ones, select('one')) }
201
+ model.should_not have_scope(:ones).select('id')
202
+ end
203
+ end
204
+
205
+ context 'with a where value' do
206
+ it 'matches if the scope exists and the query matches' do
207
+ model.instance_eval { scope(:no_one, where(:one => nil)) }
208
+ model.should have_scope(:no_one).where(:one => nil)
209
+ end
210
+
211
+ it 'does not match if the query does not match' do
212
+ model.instance_eval { scope(:no_one, where(:one => nil)) }
213
+ model.should_not have_scope(:no_one).where(:one => 1)
214
+ end
215
+ end
216
+
217
+ context 'with option :with' do
218
+ it 'matches if the query matches for the given value' do
219
+ model.instance_eval { scope(:which_one, lambda { |one| where(:one => one) }) }
220
+ model.should have_scope(:which_one, :with => 1).where(:one => 1)
221
+ end
222
+
223
+ it 'does not match if the query does not match for the given value' do
224
+ model.instance_eval { scope(:which_one, lambda { |one| where(:one => one) }) }
225
+ model.should_not have_scope(:which_one, :with => 2).where(:one => 1)
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe :validate_associated do
4
+ let(:validator_name) { :associated }
5
+ let(:attribute) { :company }
6
+ let(:default_options) { { :on => :create } }
7
+
8
+ before do
9
+ create_table(:users) { |table| table.string(:company_id) }
10
+ end
11
+
12
+ it_behaves_like 'a validation matcher' do
13
+ let(:model) do
14
+ build_class(:User, ActiveRecord::Base) do
15
+ include ActiveRecord::Validations
16
+
17
+ belongs_to :company
18
+ end
19
+ end
20
+
21
+ with_option(:message, 'is not unique!', 'invalid')
22
+ with_option(:on, :create, :update)
23
+
24
+ with_conditional_option(:if)
25
+ with_conditional_option(:unless)
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe :validate_uniqueness_of do
4
+ let(:validator_name) { :uniqueness }
5
+ let(:default_options) { { :scope => :two } }
6
+
7
+ it_behaves_like 'a validation matcher' do
8
+ let(:model) do
9
+ build_class(:User) do
10
+ include ActiveRecord::Validations
11
+ end
12
+ end
13
+
14
+ with_option(:allow_blank, true, false)
15
+ with_option(:allow_nil, true, false)
16
+ with_option(:case_sensitive, true, false)
17
+ with_option(:message, 'is not unique!', 'invalid')
18
+ with_option(:scope, :two, :three)
19
+
20
+ with_conditional_option(:if)
21
+ with_conditional_option(:unless)
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_model'
2
+ require 'active_record'
3
+
4
+ $:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
5
+ require 'remockable'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc.,
8
+ # in ./support/ and its subdirectories.
9
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each do |file|
10
+ require(file)
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ config.mock_with :rspec
15
+ config.filter_run :focus => true
16
+ config.run_all_when_everything_filtered = true
17
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveRecordExampleGroup
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ metadata[:type] = :active_record
6
+
7
+ let(:rake) { Rake::Application.new }
8
+ let(:task) { rake[self.class.description] }
9
+ let(:namespace) { self.class.description.split(':').first }
10
+
11
+ before do
12
+ ActiveRecord::Base.establish_connection(
13
+ :adapter => 'sqlite3',
14
+ :database => ':memory:'
15
+ )
16
+ end
17
+ end
18
+
19
+ RSpec.configure do |config|
20
+ config.include self, :example_group => { :file_path => /spec\/active_record/ }
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module ClassBuilder
2
+ def build_class(name, superclass=Object, &block)
3
+ Class.new(superclass) do
4
+ class_eval <<-EVAL
5
+ def self.name
6
+ '#{name}'
7
+ end
8
+ EVAL
9
+
10
+ class_eval(&block) if block_given?
11
+ end
12
+ end
13
+
14
+ def create_table(table_name, options={}, &block)
15
+ begin
16
+ drop_table(table_name)
17
+ ActiveRecord::Base.connection.create_table(table_name, options, &block)
18
+ (@created_tables ||= []) << table_name
19
+ rescue
20
+ drop_table(table_name)
21
+ raise $!
22
+ end
23
+ end
24
+
25
+ def drop_table(table_name)
26
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{table_name}")
27
+ end
28
+
29
+ def drop_created_tables
30
+ if @created_tables
31
+ @created_tables.each { |table_name| drop_table(table_name) }
32
+ end
33
+ end
34
+ end
35
+
36
+ RSpec.configure do |config|
37
+ config.include(ClassBuilder)
38
+ config.after { drop_created_tables }
39
+ end
@@ -0,0 +1,130 @@
1
+ shared_examples_for 'a validation matcher' do
2
+ let(:attribute) { :one }
3
+ let(:options) { default_options }
4
+ let(:matcher_name) { self.class.parent.parent.description }
5
+
6
+ let(:model) do
7
+ build_class(:User) do
8
+ include ActiveModel::Validations
9
+ end
10
+ end
11
+
12
+ before do
13
+ model.validates(attribute, validator_name => options)
14
+ end
15
+
16
+ subject { model.new }
17
+
18
+ def self.with_option(option_name, positive, negative, exclusive=false)
19
+ context "with option #{option_name.inspect}" do
20
+ let(:options) do
21
+ option = { option_name => positive }
22
+ merge_with_default = !exclusive && default_options.is_a?(Hash)
23
+ merge_with_default ? default_options.merge(option) : option
24
+ end
25
+
26
+ it 'matches if the options match' do
27
+ should send(matcher_name, attribute, option_name => positive)
28
+ end
29
+
30
+ it 'does not match if the options do not match' do
31
+ should_not send(matcher_name, attribute, option_name => negative)
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.with_option!(option_name, positive, negative)
37
+ with_option(option_name, positive, negative, true)
38
+ end
39
+
40
+ def self.with_conditional_option(option_name)
41
+ context "with option #{option_name.inspect} with a symbol" do
42
+ let(:options) {
43
+ option = { option_name => :returns_true }
44
+ default_options.is_a?(Hash) ? default_options.merge(option) : option
45
+ }
46
+
47
+ it 'matches if the options match' do
48
+ should send(matcher_name, attribute, option_name => :returns_true)
49
+ end
50
+
51
+ it 'does not match if the options do not match' do
52
+ should_not send(matcher_name, attribute, option_name => :returns_false)
53
+ end
54
+ end
55
+
56
+ context "with option #{option_name.inspect} with a procedure" do
57
+ let(:procedure) { lambda { |record| record.skip_validations } }
58
+
59
+ let(:options) {
60
+ option = { option_name => procedure }
61
+ default_options.is_a?(Hash) ? default_options.merge(option) : option
62
+ }
63
+
64
+ it 'matches if the options match' do
65
+ subject.stub(:skip_validations).and_return(true)
66
+ should send(matcher_name, attribute, option_name => true)
67
+ end
68
+
69
+ it 'does not match if the options do not match' do
70
+ subject.stub(:skip_validations).and_return(false)
71
+ should_not send(matcher_name, attribute, option_name => true)
72
+ end
73
+ end
74
+ end
75
+
76
+ def self.with_unsupported_option(option_name, value=nil)
77
+ context "with unsupported option #{option_name.inspect}" do
78
+ it 'raises an error' do
79
+ expect {
80
+ send(matcher_name, option_name => value)
81
+ }.to raise_error(ArgumentError, /unsupported.*:#{option_name}/i)
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'description' do
87
+ let(:matcher) { send(matcher_name, attribute) }
88
+
89
+ it 'has a custom description' do
90
+ name = matcher.instance_variable_get(:@name).to_s.gsub(/_/, ' ')
91
+ with = " with #{matcher.expected}" if matcher.expected.any?
92
+
93
+ matcher.description.should == "#{name} #{attribute}#{with}"
94
+ end
95
+ end
96
+
97
+ context 'failure messages' do
98
+ let(:matcher) { send(matcher_name, attribute) }
99
+
100
+ before { matcher.matches?(subject) }
101
+
102
+ it 'has a custom failure message' do
103
+ matcher.failure_message_for_should.should ==
104
+ "Expected #{subject.class.name} to #{matcher.description}"
105
+ end
106
+
107
+ it 'has a custom negative failure message' do
108
+ matcher.failure_message_for_should_not.should ==
109
+ "Did not expect #{subject.class.name} to #{matcher.description}"
110
+ end
111
+ end
112
+
113
+ context 'without options' do
114
+ it 'matches if the validator has been defined' do
115
+ should send(matcher_name, :one)
116
+ end
117
+
118
+ it 'does not match if the validator has not been defined' do
119
+ should_not send(matcher_name, :two)
120
+ end
121
+ end
122
+
123
+ context 'with an unknown option' do
124
+ it 'raises an error' do
125
+ expect {
126
+ send(matcher_name, :xxx => true)
127
+ }.to raise_error(ArgumentError, /unknown.*:xxx/i)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,53 @@
1
+ shared_examples_for 'an Active Record matcher' do
2
+ let(:matcher_name) { self.class.parent.parent.description }
3
+
4
+ def self.with_option(option_name, positive, negative)
5
+ context "with option #{option_name.inspect}" do
6
+ let(:options) { [:company, { option_name => positive }] }
7
+
8
+ before { model.send(macro, *options) }
9
+
10
+ it 'matches if the options match' do
11
+ should send(matcher_name, :company, option_name => positive)
12
+ end
13
+
14
+ it 'does not match if the options do not match' do
15
+ should_not send(matcher_name, :company, option_name => negative)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.with_unsupported_option(option_name, value=nil)
21
+ context "with unsupported option #{option_name.inspect}" do
22
+ it 'raises an error' do
23
+ expect {
24
+ send(matcher_name, :company, option_name => value)
25
+ }.to raise_error(ArgumentError, /unsupported.*:#{option_name}/i)
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'failure messages' do
31
+ let(:matcher) { send(matcher_name, *options) }
32
+
33
+ before { matcher.matches?(subject) }
34
+
35
+ it 'has a custom failure message' do
36
+ matcher.failure_message_for_should.should ==
37
+ "Expected #{subject.class.name} to #{matcher.description}"
38
+ end
39
+
40
+ it 'has a custom negative failure message' do
41
+ matcher.failure_message_for_should_not.should ==
42
+ "Did not expect #{subject.class.name} to #{matcher.description}"
43
+ end
44
+ end
45
+
46
+ context 'with an unknown option' do
47
+ it 'raises an error' do
48
+ expect {
49
+ send(matcher_name, :xxx => true)
50
+ }.to raise_error(ArgumentError, /unknown.*:xxx/i)
51
+ end
52
+ end
53
+ end