remockable 0.1.1 → 0.1.2

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