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.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/LICENSE +1 -1
- data/README.markdown +50 -36
- data/Rakefile +1 -0
- data/lib/remockable/active_model/helpers.rb +2 -0
- data/lib/remockable/active_record/helpers.rb +2 -0
- data/lib/remockable/version.rb +1 -1
- data/remockable.gemspec +23 -0
- data/spec/active_model/allow_mass_assignment_of_spec.rb +86 -0
- data/spec/active_model/allow_values_for_spec.rb +46 -0
- data/spec/active_model/validate_acceptance_of_spec.rb +16 -0
- data/spec/active_model/validate_confirmation_of_spec.rb +14 -0
- data/spec/active_model/validate_exclusion_of_spec.rb +16 -0
- data/spec/active_model/validate_format_of_spec.rb +18 -0
- data/spec/active_model/validate_inclusion_of_spec.rb +16 -0
- data/spec/active_model/validate_length_of_spec.rb +26 -0
- data/spec/active_model/validate_numericality_of_spec.rb +23 -0
- data/spec/active_model/validate_presence_of_spec.rb +14 -0
- data/spec/active_record/accept_nested_attributes_for_spec.rb +47 -0
- data/spec/active_record/belong_to_spec.rb +62 -0
- data/spec/active_record/have_and_belong_to_many_spec.rb +67 -0
- data/spec/active_record/have_column_spec.rb +109 -0
- data/spec/active_record/have_default_scope_spec.rb +169 -0
- data/spec/active_record/have_index_spec.rb +89 -0
- data/spec/active_record/have_many_spec.rb +70 -0
- data/spec/active_record/have_one_spec.rb +61 -0
- data/spec/active_record/have_scope_spec.rb +229 -0
- data/spec/active_record/validate_associated_spec.rb +27 -0
- data/spec/active_record/validate_uniqueness_of_spec.rb +23 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/active_record_example_group.rb +22 -0
- data/spec/support/class_builder.rb +39 -0
- data/spec/support/shared/a_validation_matcher.rb +130 -0
- data/spec/support/shared/an_active_record_matcher.rb +53 -0
- metadata +134 -40
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
CHANGED
data/README.markdown
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Remockable
|
2
2
|
|
3
|
+
[![Build Status][travis-image]][travis]
|
4
|
+
|
5
|
+
[travis]: http://travis-ci.org/tylerhunt/remockable
|
6
|
+
[travis-image]: https://secure.travis-ci.org/tylerhunt/remockable.png
|
7
|
+
|
3
8
|
A collection of RSpec 2 matchers to simplify your web app specs.
|
4
9
|
|
5
10
|
|
@@ -7,7 +12,7 @@ A collection of RSpec 2 matchers to simplify your web app specs.
|
|
7
12
|
|
8
13
|
The goal of this project is to provide a modern replacement to the now
|
9
14
|
unmaintained Remarkable project. Remarkable was a great asset when Rails 2.3
|
10
|
-
was current, but now that Rails 3
|
15
|
+
was current, but now that Rails 3 has become mainstream, a gap has been left
|
11
16
|
by still unreleased Remarkable 4.0.
|
12
17
|
|
13
18
|
In looking at the code for Remarkable to determine the feasibility of continuing
|
@@ -17,58 +22,67 @@ Remockable was born. It's an attempt to start with a clean slate but maintain
|
|
17
22
|
the original goal of Remarkable in spirit.
|
18
23
|
|
19
24
|
|
20
|
-
##
|
25
|
+
## Installation
|
21
26
|
|
22
|
-
|
27
|
+
Add this line to your application's Gemfile:
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
* validate_exclusion_of
|
28
|
-
* validate_format_of
|
29
|
-
* validate_inclusion_of
|
30
|
-
* validate_length_of
|
31
|
-
* validate_numericality_of
|
32
|
-
* validate_presence_of
|
29
|
+
``` ruby
|
30
|
+
gem 'remockable'
|
31
|
+
```
|
33
32
|
|
33
|
+
And then execute:
|
34
34
|
|
35
|
-
|
35
|
+
$ bundle
|
36
36
|
|
37
|
-
|
37
|
+
Or install it yourself as:
|
38
38
|
|
39
|
-
|
40
|
-
* have_index
|
41
|
-
* have_scope
|
42
|
-
* have_default_scope
|
43
|
-
* belong_to
|
44
|
-
* have_one
|
45
|
-
* have_many
|
46
|
-
* have_and_belong_to_many
|
47
|
-
* validate_associated
|
48
|
-
* validate_uniqueness_of
|
39
|
+
$ gem install remockable
|
49
40
|
|
50
41
|
|
51
|
-
##
|
42
|
+
## Usage
|
43
|
+
|
44
|
+
Remockable provides matchers for use with Active Model and Active Record
|
45
|
+
classes.
|
52
46
|
|
53
|
-
|
47
|
+
### Active Model Matchers
|
54
48
|
|
55
|
-
|
49
|
+
The following Active Model matchers are supported:
|
56
50
|
|
57
|
-
|
51
|
+
* `allow_mass_assignment_of`
|
52
|
+
* `validate_acceptance_of`
|
53
|
+
* `validate_confirmation_of`
|
54
|
+
* `validate_exclusion_of`
|
55
|
+
* `validate_format_of`
|
56
|
+
* `validate_inclusion_of`
|
57
|
+
* `validate_length_of`
|
58
|
+
* `validate_numericality_of`
|
59
|
+
* `validate_presence_of`
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
### Active Record Matchers
|
62
|
+
|
63
|
+
The following Active Record matchers are supported:
|
62
64
|
|
63
|
-
|
65
|
+
* `have_column`
|
66
|
+
* `have_index`
|
67
|
+
* `have_scope`
|
68
|
+
* `have_default_scope`
|
69
|
+
* `belong_to`
|
70
|
+
* `have_one`
|
71
|
+
* `have_many`
|
72
|
+
* `have_and_belong_to_many`
|
73
|
+
* `validate_associated`
|
74
|
+
* `validate_uniqueness_of`
|
64
75
|
|
65
76
|
|
66
|
-
##
|
77
|
+
## Contributing
|
67
78
|
|
68
|
-
|
69
|
-
|
79
|
+
1. Fork it.
|
80
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
81
|
+
3. Commit your changes (`git commit -am 'Added some feature'`).
|
82
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
83
|
+
5. Create a new Pull Request.
|
70
84
|
|
71
85
|
|
72
86
|
## Copyright
|
73
87
|
|
74
|
-
Copyright © 2010-
|
88
|
+
Copyright © 2010-2012 Tyler Hunt. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/remockable/version.rb
CHANGED
data/remockable.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require './lib/remockable/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'remockable'
|
5
|
+
spec.version = Remockable::VERSION
|
6
|
+
spec.authors = ['Tyler Hunt']
|
7
|
+
spec.summary = 'A collection of RSpec matchers for web apps.'
|
8
|
+
spec.homepage = 'http://github.com/tylerhunt/remockable'
|
9
|
+
spec.license = 'MIT'
|
10
|
+
|
11
|
+
spec.add_dependency 'activemodel', '~> 3.0'
|
12
|
+
spec.add_dependency 'activerecord', '~> 3.0'
|
13
|
+
spec.add_dependency 'activesupport', '~> 3.0'
|
14
|
+
spec.add_dependency 'rspec-core', '~> 2.0'
|
15
|
+
spec.add_dependency 'rspec-expectations', '~> 2.0'
|
16
|
+
spec.add_dependency 'rspec-mocks', '~> 2.0'
|
17
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3.4'
|
18
|
+
|
19
|
+
spec.files = `git ls-files`.split($/)
|
20
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |file| File.basename(file) }
|
21
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :allow_mass_assignment_of do
|
4
|
+
let(:attribute) { :one }
|
5
|
+
let(:options) { {} }
|
6
|
+
let(:matcher_name) { self.class.parent.description }
|
7
|
+
|
8
|
+
let(:model) do
|
9
|
+
build_class(:User) { include ActiveModel::MassAssignmentSecurity }
|
10
|
+
end
|
11
|
+
|
12
|
+
subject { model.new }
|
13
|
+
|
14
|
+
context 'description' do
|
15
|
+
it 'has a custom description' do
|
16
|
+
matcher = allow_mass_assignment_of(attribute)
|
17
|
+
matcher.description.should == "allow mass-assignment of #{attribute}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'failure messages' do
|
22
|
+
let(:matcher) { allow_mass_assignment_of(attribute) }
|
23
|
+
|
24
|
+
before { matcher.matches?(subject) }
|
25
|
+
|
26
|
+
it 'has a custom failure message' do
|
27
|
+
matcher.failure_message_for_should.should ==
|
28
|
+
"Expected #{subject.class.name} to #{matcher.description}"
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has a custom negative failure message' do
|
32
|
+
matcher.failure_message_for_should_not.should ==
|
33
|
+
"Did not expect #{subject.class.name} to #{matcher.description}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'for accessible' do
|
38
|
+
before { model.attr_accessible(attribute, options) }
|
39
|
+
|
40
|
+
it 'matches if the attribute is accessible' do
|
41
|
+
should allow_mass_assignment_of(:one)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'does not match if the attribute is protected' do
|
45
|
+
should_not allow_mass_assignment_of(:two)
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with a role' do
|
49
|
+
let(:options) { { :as => :admin } }
|
50
|
+
|
51
|
+
it 'matches if the attribute is accessible' do
|
52
|
+
should allow_mass_assignment_of(:one, :as => :admin)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not match if the attribute is protected' do
|
56
|
+
should_not allow_mass_assignment_of(:two, :as => :admin)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'for protected' do
|
62
|
+
let(:attribute) { :two }
|
63
|
+
|
64
|
+
before { model.attr_protected(attribute, options) }
|
65
|
+
|
66
|
+
it 'matches if the attribute is accessible' do
|
67
|
+
should allow_mass_assignment_of(:one)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'does not match if the attribute is protected' do
|
71
|
+
should_not allow_mass_assignment_of(:two)
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'with a role' do
|
75
|
+
let(:options) { { :as => :admin } }
|
76
|
+
|
77
|
+
it 'matches if the attribute is accessible' do
|
78
|
+
should allow_mass_assignment_of(:one, :as => :admin)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'does not match if the attribute is protected' do
|
82
|
+
should_not allow_mass_assignment_of(:two, :as => :admin)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :allow_values_for do
|
4
|
+
let(:attribute) { :one }
|
5
|
+
let(:values) { ['123'] }
|
6
|
+
let(:matcher_name) { self.class.parent.description }
|
7
|
+
|
8
|
+
let(:model) do
|
9
|
+
build_class(:User) { include ActiveModel::Validations }
|
10
|
+
end
|
11
|
+
|
12
|
+
before { model.validates(attribute, :format => /^\d+$/) }
|
13
|
+
|
14
|
+
subject { model.new }
|
15
|
+
|
16
|
+
context 'description' do
|
17
|
+
it 'has a custom description' do
|
18
|
+
matcher = allow_values_for(attribute, *values)
|
19
|
+
matcher.description.should == "allow the values #{values.collect(&:inspect).to_sentence} for #{attribute}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'failure messages' do
|
24
|
+
let(:matcher) { allow_values_for(attribute, *values) }
|
25
|
+
|
26
|
+
before { matcher.matches?(subject) }
|
27
|
+
|
28
|
+
it 'has a custom failure message' do
|
29
|
+
matcher.failure_message_for_should.should ==
|
30
|
+
"Expected #{subject.class.name} to #{matcher.description}"
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has a custom negative failure message' do
|
34
|
+
matcher.failure_message_for_should_not.should ==
|
35
|
+
"Did not expect #{subject.class.name} to #{matcher.description}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'matches if the values are valid' do
|
40
|
+
should allow_values_for(:one, '123', '4567890')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not match if the values are invalid' do
|
44
|
+
should_not allow_values_for(:one, 'abc', 'abc123')
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_acceptance_of do
|
4
|
+
let(:validator_name) { :acceptance }
|
5
|
+
let(:default_options) { true }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:accept, 'TRUE', 'FALSE')
|
9
|
+
with_option(:allow_nil, true, false)
|
10
|
+
with_option(:message, 'must agree!', 'invalid')
|
11
|
+
with_option(:on, :create, :update)
|
12
|
+
|
13
|
+
with_conditional_option(:if)
|
14
|
+
with_conditional_option(:unless)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_confirmation_of do
|
4
|
+
let(:validator_name) { :confirmation }
|
5
|
+
let(:default_options) { true }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:message, 'must match!', 'invalid')
|
9
|
+
with_option(:on, :create, :update)
|
10
|
+
|
11
|
+
with_conditional_option(:if)
|
12
|
+
with_conditional_option(:unless)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_exclusion_of do
|
4
|
+
let(:validator_name) { :exclusion }
|
5
|
+
let(:default_options) { { :in => [true, false] } }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:allow_blank, true, false)
|
9
|
+
with_option(:allow_nil, true, false)
|
10
|
+
with_option(:in, [true, false], %w(male female))
|
11
|
+
with_option(:message, 'is in list!', 'invalid')
|
12
|
+
|
13
|
+
with_conditional_option(:if)
|
14
|
+
with_conditional_option(:unless)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_format_of do
|
4
|
+
let(:validator_name) { :format }
|
5
|
+
let(:default_options) { { :with => /\d+/ } }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:allow_blank, true, false)
|
9
|
+
with_option(:allow_nil, true, false)
|
10
|
+
with_option(:message, 'is the wrong format!', 'invalid')
|
11
|
+
with_option(:on, :create, :update)
|
12
|
+
with_option!(:with, /\d+/, /\w+/)
|
13
|
+
with_option!(:without, /\d+/, /\w+/)
|
14
|
+
|
15
|
+
with_conditional_option(:if)
|
16
|
+
with_conditional_option(:unless)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_inclusion_of do
|
4
|
+
let(:validator_name) { :inclusion }
|
5
|
+
let(:default_options) { { :in => [true, false] } }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:allow_blank, true, false)
|
9
|
+
with_option(:allow_nil, true, false)
|
10
|
+
with_option(:in, [true, false], %w(male female))
|
11
|
+
with_option(:message, 'is not in list!', 'invalid')
|
12
|
+
|
13
|
+
with_conditional_option(:if)
|
14
|
+
with_conditional_option(:unless)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_length_of do
|
4
|
+
let(:validator_name) { :length }
|
5
|
+
let(:default_options) { { :is => 5 } }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:allow_blank, true, false)
|
9
|
+
with_option(:allow_nil, true, false)
|
10
|
+
with_option(:in, 1..10, 1..5)
|
11
|
+
with_option(:is, 5, 10)
|
12
|
+
with_option(:maximum, 5, 10)
|
13
|
+
with_option(:message, 'is the wrong length!', 'invalid')
|
14
|
+
with_option(:minimum, 5, 10)
|
15
|
+
with_option(:on, :create, :update)
|
16
|
+
with_option(:too_long, 'is too long!', 'invalid')
|
17
|
+
with_option(:too_short, 'is too short!', 'invalid')
|
18
|
+
with_option(:within, 1..10, 1..5)
|
19
|
+
with_option(:wrong_length, 'is not five!', 'invalid')
|
20
|
+
|
21
|
+
with_conditional_option(:if)
|
22
|
+
with_conditional_option(:unless)
|
23
|
+
|
24
|
+
with_unsupported_option(:tokenizer, lambda { |string| string.scan(/\w+/) })
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_numericality_of do
|
4
|
+
let(:validator_name) { :numericality }
|
5
|
+
let(:default_options) { { :only_integer => true } }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher' do
|
8
|
+
with_option(:allow_nil, true, false)
|
9
|
+
with_option(:equal_to, 5, 10)
|
10
|
+
with_option(:even, true, false)
|
11
|
+
with_option(:greater_than, 5, 10)
|
12
|
+
with_option(:greater_than_or_equal_to, 5, 10)
|
13
|
+
with_option(:less_than, 5, 10)
|
14
|
+
with_option(:less_than_or_equal_to, 5, 10)
|
15
|
+
with_option(:message, 'is the wrong numericality!', 'invalid')
|
16
|
+
with_option(:odd, true, false)
|
17
|
+
with_option(:on, :create, :update)
|
18
|
+
with_option(:only_integer, true, false)
|
19
|
+
|
20
|
+
with_conditional_option(:if)
|
21
|
+
with_conditional_option(:unless)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :validate_presence_of do
|
4
|
+
let(:validator_name) { :presence }
|
5
|
+
let(:default_options) { true }
|
6
|
+
|
7
|
+
it_behaves_like 'a validation matcher', :presence do
|
8
|
+
with_option(:message, 'is required!', 'invalid')
|
9
|
+
with_option(:on, :create, :update)
|
10
|
+
|
11
|
+
with_conditional_option(:if)
|
12
|
+
with_conditional_option(:unless)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :accept_nested_attributes_for do
|
4
|
+
let(:macro) { :accepts_nested_attributes_for }
|
5
|
+
let(:options) { [:company, { :allow_destroy => true }] }
|
6
|
+
|
7
|
+
it_behaves_like 'an Active Record matcher' do
|
8
|
+
let(:matcher_name) { :accept_nested_attributes_for }
|
9
|
+
|
10
|
+
let(:model) do
|
11
|
+
build_class(:User, ActiveRecord::Base) { belongs_to :company }
|
12
|
+
end
|
13
|
+
|
14
|
+
before { create_table(:users) }
|
15
|
+
|
16
|
+
subject { model.new }
|
17
|
+
|
18
|
+
context 'description' do
|
19
|
+
let(:matcher) { send(matcher_name, *options) }
|
20
|
+
|
21
|
+
it 'has a custom description' do
|
22
|
+
association = matcher.instance_variable_get(:@association)
|
23
|
+
with = " with #{matcher.expected.inspect}" if matcher.expected.any?
|
24
|
+
matcher.description.should == "accept nested attributes for #{association}#{with}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with no options' do
|
29
|
+
let(:options) { :company }
|
30
|
+
|
31
|
+
it 'matches if the model accepts the nested attributes' do
|
32
|
+
model.accepts_nested_attributes_for(*options)
|
33
|
+
model.should accept_nested_attributes_for(*options)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not match if the model does not accept the nested attributes' do
|
37
|
+
model.should_not accept_nested_attributes_for(*options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
with_option(:allow_destroy, true, false)
|
42
|
+
with_option(:limit, 1, 2)
|
43
|
+
with_option(:update_only, true, false)
|
44
|
+
|
45
|
+
with_unsupported_option(:reject_if, :all_blank)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :belong_to do
|
4
|
+
let(:macro) { :belongs_to }
|
5
|
+
let(:options) { [:company, { :dependent => :destroy }] }
|
6
|
+
|
7
|
+
it_behaves_like 'an Active Record matcher' do
|
8
|
+
let(:model) { build_class(:User, ActiveRecord::Base) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
create_table(:users) { |table| table.integer(:company_id) }
|
12
|
+
end
|
13
|
+
|
14
|
+
subject { model.new }
|
15
|
+
|
16
|
+
context 'description' do
|
17
|
+
let(:matcher) { send(matcher_name, *options) }
|
18
|
+
|
19
|
+
it 'has a custom description' do
|
20
|
+
name = matcher.instance_variable_get(:@name).to_s.gsub(/_/, ' ')
|
21
|
+
association = matcher.instance_variable_get(:@association)
|
22
|
+
with = " with #{matcher.expected.inspect}" if matcher.expected.any?
|
23
|
+
matcher.description.should == "#{name} #{association}#{with}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with no options' do
|
28
|
+
let(:options) { :company }
|
29
|
+
|
30
|
+
it 'matches if the association exists' do
|
31
|
+
model.belongs_to(*options)
|
32
|
+
model.should belong_to(*options)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'does not match if the association does not exist' do
|
36
|
+
model.should_not belong_to(*options)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'does not match if the association is of the wrong type' do
|
40
|
+
model.has_many(*options)
|
41
|
+
model.should_not belong_to(*options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
with_option(:class_name, 'Company', 'Organization')
|
46
|
+
with_option(:conditions, { :id => 1 }, { :id => 2 })
|
47
|
+
with_option(:select, %w(id), %w(name))
|
48
|
+
with_option(:foreign_key, :company_id, :organization_id)
|
49
|
+
with_option(:primary_key, :id, :company_id)
|
50
|
+
with_option(:dependent, :destroy, :nullify)
|
51
|
+
with_option(:counter_cache, true, false)
|
52
|
+
with_option(:include, :users, :employees)
|
53
|
+
with_option(:polymorphic, true, false)
|
54
|
+
with_option(:readonly, true, false)
|
55
|
+
with_option(:validate, true, false)
|
56
|
+
with_option(:autosave, true, false)
|
57
|
+
with_option(:touch, true, false)
|
58
|
+
with_option(:inverse_of, :users, :employees)
|
59
|
+
|
60
|
+
with_unsupported_option(:extend, Module.new)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe :have_and_belong_to_many do
|
4
|
+
let(:macro) { :has_and_belongs_to_many }
|
5
|
+
let(:options) { [:tags, { :order => :name }] }
|
6
|
+
|
7
|
+
it_behaves_like 'an Active Record matcher' do
|
8
|
+
let(:model) { build_class(:User, ActiveRecord::Base) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
create_table(:users) { |table| table.string(:name) }
|
12
|
+
end
|
13
|
+
|
14
|
+
subject { model.new }
|
15
|
+
|
16
|
+
context 'description' do
|
17
|
+
let(:matcher) { send(matcher_name, *options) }
|
18
|
+
|
19
|
+
it 'has a custom description' do
|
20
|
+
association = matcher.instance_variable_get(:@association)
|
21
|
+
with = " with #{matcher.expected.inspect}" if matcher.expected.any?
|
22
|
+
matcher.description.should == "have and belong to #{association}#{with}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with no options' do
|
27
|
+
let(:options) { :tags }
|
28
|
+
|
29
|
+
it 'matches if the association exists' do
|
30
|
+
model.has_and_belongs_to_many(*options)
|
31
|
+
model.should have_and_belong_to_many(*options)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not match if the association does not exist' do
|
35
|
+
model.should_not have_and_belong_to_many(*options)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'does not match if the association is of the wrong type' do
|
39
|
+
model.has_many(*options)
|
40
|
+
model.should_not have_and_belong_to_many(*options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
with_option(:class_name, 'Tag', 'Role')
|
45
|
+
with_option(:join_table, 'tags_users', 'user_tags')
|
46
|
+
with_option(:foreign_key, :user_id, :tagged_id)
|
47
|
+
with_option(:association_foreign_key, :tag_id, :role_id)
|
48
|
+
with_option(:conditions, { :active => true }, { :active => false })
|
49
|
+
with_option(:order, :name, :created_at)
|
50
|
+
with_option(:uniq, true, false)
|
51
|
+
with_option(:finder_sql, 'SELECT * FROM user_tags', 'SELECT * FROM tags_users')
|
52
|
+
with_option(:counter_sql, 'SELECT COUNT(*) FROM user_tags', 'SELECT COUNT(*) FROM tags_users')
|
53
|
+
with_option(:delete_sql, 'DELETE FROM user_tags', 'DELETE FROM tags_users')
|
54
|
+
with_option(:insert_sql, 'INSERT INTO user_tags', 'INSERT INTO tags_users')
|
55
|
+
with_option(:include, :statistics, :changes)
|
56
|
+
with_option(:group, 'usage_count', 'changes_count')
|
57
|
+
with_option(:having, 'usage_count > 5', 'changes_count > 5')
|
58
|
+
with_option(:limit, 5, 10)
|
59
|
+
with_option(:offset, 10, 20)
|
60
|
+
with_option(:select, %w(id), %w(name))
|
61
|
+
with_option(:readonly, true, false)
|
62
|
+
with_option(:validate, true, false)
|
63
|
+
with_option(:autosave, true, false)
|
64
|
+
|
65
|
+
with_unsupported_option(:extend, Module.new)
|
66
|
+
end
|
67
|
+
end
|