smart_rspec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +206 -0
- data/Rakefile +2 -0
- data/lib/smart_rspec/macros.rb +34 -0
- data/lib/smart_rspec/matchers.rb +45 -0
- data/lib/smart_rspec/support/assertions.rb +125 -0
- data/lib/smart_rspec/support/expectations.rb +19 -0
- data/lib/smart_rspec/support/regexes.rb +44 -0
- data/lib/smart_rspec/version.rb +3 -0
- data/lib/smart_rspec.rb +19 -0
- data/smart_rspec.gemspec +28 -0
- data/spec/factories/user.rb +88 -0
- data/spec/smart_rspec/macros_spec.rb +76 -0
- data/spec/smart_rspec/matchers_spec.rb +144 -0
- data/spec/spec_helper.rb +8 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 56204d08a3ee3d45091216ca50b197a4d9b08095
|
4
|
+
data.tar.gz: dd3431e077d7ef0a94d6df2e8df66779f14c1705
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b5a735e0e33fb63863c343ad17b3a5daeb0282cc759d7a1852cc2a7c734db2d69cb58cea1724548841abe331b1b1d9261eb38d0a8099a08f679c13b8c33d5be6
|
7
|
+
data.tar.gz: 3afea96381aeb7aed7dedf20eabd1814e07e9cb08731760deba0fd8b10aef4de2447f41f4244b45e35445264107a1377763584badc84cd5ad903247ed12db72b
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Tiago Guedes
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# SmartRspec
|
2
|
+
|
3
|
+
It's time to make your specs even more awesome! SmartRspec adds useful macros and matchers into the RSpec's test suite, this way you can quickly define specs for your Rails app and get focused on making things turn into green.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Compatible with Ruby 1.9+
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'smart_rspec'
|
12
|
+
|
13
|
+
Execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Require the gem at the top of your `spec/rails_helper.rb` (or equivalent):
|
18
|
+
``` ruby
|
19
|
+
require 'smart_rspec'
|
20
|
+
```
|
21
|
+
|
22
|
+
Then include the SmartRspec module:
|
23
|
+
|
24
|
+
``` ruby
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.include SmartRspec
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
* [Macros](#macros)
|
33
|
+
* [has_attributes](#has_attributes)
|
34
|
+
* [belongs_to, has_one, has_many](#belongs_to-has_one-has_many)
|
35
|
+
* [fails_validation_of](#fails_validation_of)
|
36
|
+
* [Matchers](#matchers)
|
37
|
+
* [be_ascending](#be_ascending)
|
38
|
+
* [be_descending](#be_descending)
|
39
|
+
* [be_boolean](#be_boolean)
|
40
|
+
* [be_email](#be_email)
|
41
|
+
* [be_url](#be_url)
|
42
|
+
* [be_image_url](#be_image_url)
|
43
|
+
* [have](#have)
|
44
|
+
* [have_at_least](#have_at_least)
|
45
|
+
* [have_at_most](#have_at_most)
|
46
|
+
* [have_error_on](#have_error_on)
|
47
|
+
* [include_items](#include_items)
|
48
|
+
|
49
|
+
### Macros
|
50
|
+
|
51
|
+
You will just need to define a valid `subject` to start using SmartRspec's macros in your spec file.
|
52
|
+
|
53
|
+
#### has_attributes
|
54
|
+
|
55
|
+
It builds specs for model attributes and test its type, enumerated values and defaults:
|
56
|
+
``` ruby
|
57
|
+
RSpec.describe User, type: :model do
|
58
|
+
subject { FactoryGirl.build(:user) }
|
59
|
+
|
60
|
+
has_attributes :email, type: :String
|
61
|
+
has_attributes :is_admin, type: :Boolean
|
62
|
+
has_attributes :score, type: :Integer, default: 0
|
63
|
+
has_attributes :locale, type: :String, enum: %i(en pt), default: 'en'
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
#### belongs_to, has_one, has_many
|
68
|
+
|
69
|
+
It builds specs and test model associations like `belongs_to`, `has_one` and `has_many`.
|
70
|
+
``` ruby
|
71
|
+
RSpec.describe User, type: :model do
|
72
|
+
subject { FactoryGirl.build(:user) }
|
73
|
+
|
74
|
+
belongs_to :business
|
75
|
+
has_one :project
|
76
|
+
has_many :tasks
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
#### fails_validation_of
|
81
|
+
|
82
|
+
It builds specs forcing model validations to fail, it means that you will only turn its specs into green when you specify the corresponding validation in the model.
|
83
|
+
|
84
|
+
``` ruby
|
85
|
+
RSpec.describe User, type: :model do
|
86
|
+
subject { FactoryGirl.build(:user) }
|
87
|
+
|
88
|
+
fails_validation_of :email, presence: true, email: true
|
89
|
+
fails_validation_of :name, length: { maximum: 80 }, uniqueness: true
|
90
|
+
fails_validation_of :username, length: { minimum: 10 }, exclusion: { in: %w(foo bar) }
|
91
|
+
# Other validations...
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
The `fails_validation_of` implements specs for the following validations:
|
96
|
+
|
97
|
+
- `presence`
|
98
|
+
- `email`
|
99
|
+
- `length`
|
100
|
+
- `exclusion`
|
101
|
+
- `inclusion`
|
102
|
+
- `uniqueness`
|
103
|
+
- `format`
|
104
|
+
|
105
|
+
In two cases it will require a valid mock to be passed so SmartRspec can use it to force the validation to fail properly.
|
106
|
+
|
107
|
+
For uniqueness with scope:
|
108
|
+
``` ruby
|
109
|
+
other_user = FactoryGirl.build(:other_valid_user)
|
110
|
+
fails_validation_of :username, uniqueness: { scope: :name, mock: other_user }
|
111
|
+
```
|
112
|
+
|
113
|
+
For format:
|
114
|
+
``` ruby
|
115
|
+
fails_validation_of :foo, format: { with: /foo/, mock: 'bar' }
|
116
|
+
```
|
117
|
+
|
118
|
+
### Matchers
|
119
|
+
|
120
|
+
SmartRspec gathers a collection of custom useful matchers:
|
121
|
+
|
122
|
+
#### be_ascending
|
123
|
+
|
124
|
+
``` ruby
|
125
|
+
it { expect([1, 2, 3, 4]).to be_ascending }
|
126
|
+
it { expect([1, 4, 2, 3]).not_to be_ascending }
|
127
|
+
```
|
128
|
+
|
129
|
+
#### be_descending
|
130
|
+
``` ruby
|
131
|
+
it { expect([4, 3, 2, 1]).to be_descending }
|
132
|
+
it { expect([1, 2, 3, 4]).not_to be_descending }
|
133
|
+
```
|
134
|
+
|
135
|
+
#### be_boolean
|
136
|
+
``` ruby
|
137
|
+
it { expect(true).to be_boolean }
|
138
|
+
it { expect('true').not_to be_boolean }
|
139
|
+
```
|
140
|
+
|
141
|
+
#### be_email
|
142
|
+
``` ruby
|
143
|
+
it { expect('tiagopog@gmail.com').to be_email }
|
144
|
+
it { expect('tiagopog@gmail').not_to be_email }
|
145
|
+
```
|
146
|
+
|
147
|
+
#### be_url
|
148
|
+
``` ruby
|
149
|
+
it { expect('http://adtangerine.com').to be_url }
|
150
|
+
it { expect('adtangerine.com').not_to be_url }
|
151
|
+
```
|
152
|
+
|
153
|
+
#### be_image_url
|
154
|
+
``` ruby
|
155
|
+
it { expect('http://adtangerine.com/foobar.png').to be_image_url }
|
156
|
+
it { expect('http://adtangerine.com/foobar.jpg').not_to be_image_url(:gif) }
|
157
|
+
it { expect('http://adtangerine.com/foo/bar').not_to be_image_url }
|
158
|
+
```
|
159
|
+
|
160
|
+
#### have(x).items
|
161
|
+
``` ruby
|
162
|
+
it { expect([1]).to have(1).item }
|
163
|
+
it { expect(%w(foo bar)).to have(2).items }
|
164
|
+
it { expect(%w(foo bar)).not_to have(1).item }
|
165
|
+
```
|
166
|
+
|
167
|
+
#### have_at_least
|
168
|
+
``` ruby
|
169
|
+
it { expect(%w(foo bar foobar)).to have_at_least(3).items }
|
170
|
+
```
|
171
|
+
|
172
|
+
#### have_at_most
|
173
|
+
``` ruby
|
174
|
+
it { expect(%w(foo bar foobar)).to have_at_most(3).items }
|
175
|
+
```
|
176
|
+
#### have_error_on
|
177
|
+
``` ruby
|
178
|
+
subject(:user) { FactoryGirl.build(:user, email: nil) }
|
179
|
+
|
180
|
+
it 'has an invalid email' do
|
181
|
+
user.valid?
|
182
|
+
expect(user).to have_error_on(:email)
|
183
|
+
expect(user).not_to have_error_on(:email)
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
#### include_items
|
188
|
+
``` ruby
|
189
|
+
it { expect(%w(foo bar foobar)).to include_items(%w(foo bar foobar)) }
|
190
|
+
```
|
191
|
+
|
192
|
+
# TODO
|
193
|
+
|
194
|
+
- Create macros for model scopes;
|
195
|
+
- Create macros for controllers and requests;
|
196
|
+
- Add more matchers.
|
197
|
+
|
198
|
+
## Contributing
|
199
|
+
|
200
|
+
1. Fork it;
|
201
|
+
2. Create your feature branch (`git checkout -b my-new-feature`);
|
202
|
+
3. Create your specs and make sure they are passing;
|
203
|
+
4. Document your feature in the README.md;
|
204
|
+
4. Commit your changes (`git commit -am 'Add some feature'`);
|
205
|
+
5. Push to the branch (`git push origin my-new-feature`);
|
206
|
+
6. Create new Pull Request.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'smart_rspec/support/assertions'
|
2
|
+
|
3
|
+
module SmartRspec::Macros
|
4
|
+
include SmartRspec::Support::Assertions
|
5
|
+
|
6
|
+
def belongs_to(*associations)
|
7
|
+
assert_association :belongs_to, associations
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_attributes(*attrs)
|
11
|
+
options = attrs.last.is_a?(Hash) && attrs.last.has_key?(:type) ? attrs.pop : nil
|
12
|
+
assert_has_attributes(attrs, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_one(*associations)
|
16
|
+
assert_association :has_one, associations
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_many(*associations)
|
20
|
+
assert_association :has_many, associations
|
21
|
+
end
|
22
|
+
|
23
|
+
def fails_validation_of(*attrs, validations)
|
24
|
+
attrs.each do |attr|
|
25
|
+
context attr do
|
26
|
+
validations.keys.each do |key|
|
27
|
+
validation = validations[key]
|
28
|
+
validation && send("validates_#{key}_of", attr, validation)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rspec/matchers'
|
2
|
+
require 'rspec/expectations'
|
3
|
+
require 'rspec/collection_matchers'
|
4
|
+
require 'smart_rspec/support/regexes'
|
5
|
+
|
6
|
+
include SmartRspec::Support::Regexes
|
7
|
+
|
8
|
+
module SmartRspec
|
9
|
+
module Matchers
|
10
|
+
extend RSpec::Matchers::DSL
|
11
|
+
|
12
|
+
matcher :be_boolean do
|
13
|
+
match { |actual| [true, false].include?(actual) }
|
14
|
+
end
|
15
|
+
|
16
|
+
matcher :be_email do
|
17
|
+
match { |actual| actual =~ build_regex(:email) }
|
18
|
+
end
|
19
|
+
|
20
|
+
matcher :be_url do
|
21
|
+
match { |actual| actual =~ build_regex(:uri) }
|
22
|
+
end
|
23
|
+
|
24
|
+
matcher :be_image_url do |*types|
|
25
|
+
match { |actual| actual =~ build_regex(:image, types) }
|
26
|
+
end
|
27
|
+
|
28
|
+
matcher :have_error_on do |attr|
|
29
|
+
match { |actual| actual.errors.keys.include?(attr) }
|
30
|
+
end
|
31
|
+
|
32
|
+
matcher :include_items do |*items|
|
33
|
+
match { |actual| (items.flatten - [actual].flatten).empty? }
|
34
|
+
end
|
35
|
+
|
36
|
+
matcher :be_ascending do
|
37
|
+
match { |actual| actual == actual.sort }
|
38
|
+
end
|
39
|
+
|
40
|
+
matcher :be_descending do
|
41
|
+
match { |actual| actual.each_cons(2).all? { |i, j| i >= j } }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module SmartRspec
|
2
|
+
module Support
|
3
|
+
module Assertions
|
4
|
+
private_class_method
|
5
|
+
|
6
|
+
def validates_email_of(attr, validation)
|
7
|
+
it 'has an invalid format' do
|
8
|
+
%w(foobar foobar@ @foobar foo@bar).each do |e|
|
9
|
+
assert_validation(attr, e)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def validates_exclusion_of(attr, validation)
|
15
|
+
it 'has a reserved value' do
|
16
|
+
assert_validation(attr, validation[:in].sample)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def validates_format_of(attr, validation)
|
21
|
+
it 'does not match the required format' do
|
22
|
+
mock, with =
|
23
|
+
validation.values_at(:mock).first,
|
24
|
+
validation.values_at(:with).first
|
25
|
+
|
26
|
+
if mock && with && with !~ mock
|
27
|
+
assert_validation(attr, mock)
|
28
|
+
else
|
29
|
+
raise ArgumentError, ':with and :mock are required when using the :format validation'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def validates_inclusion_of(attr, validation)
|
35
|
+
it 'is out of the scope of possible values' do
|
36
|
+
begin
|
37
|
+
value = SecureRandom.hex
|
38
|
+
end while validation[:in].include?(value)
|
39
|
+
assert_validation(attr, value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def validates_length_of(attr, validation)
|
44
|
+
validation.each do |key, value|
|
45
|
+
next unless %i(in is maximum minimum within).include?(key)
|
46
|
+
txt, n = build_length_validation(key, value)
|
47
|
+
it txt do
|
48
|
+
assert_validation(attr, 'x' * n)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def validates_presence_of(attr, validation)
|
54
|
+
it 'is blank' do
|
55
|
+
assert_validation(attr, nil)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def validates_uniqueness_of(attr, validation)
|
60
|
+
scoped = scoped_validation?(validation)
|
61
|
+
scope = scoped ? validation[:scope] : nil
|
62
|
+
|
63
|
+
it "is already in use#{" (scope: #{scope})" if scoped}" do
|
64
|
+
mock = (validation[:mock] rescue nil) || subject.dup
|
65
|
+
scoped && mock.send("#{scope}=", subject.send(scope))
|
66
|
+
|
67
|
+
assert_validation(attr, subject.send(attr), mock)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_has_attributes(attrs, options)
|
72
|
+
type_str = build_type_str(options)
|
73
|
+
default, enum, type = options.values_at(:default, :enum, :type)
|
74
|
+
|
75
|
+
attrs.each do |attr|
|
76
|
+
it %Q(has an attribute named "#{attr}"#{type_str}) do
|
77
|
+
expect(subject).to respond_to(attr)
|
78
|
+
default && (expect(subject.send(attr)).to eq(default))
|
79
|
+
enum && (expect(enum).to include(subject.send(attr).to_sym))
|
80
|
+
type && assert_attr_type(attr, type, options)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def assert_association(assoc_type, associations)
|
86
|
+
associations.each do |association|
|
87
|
+
it "#{assoc_type.to_s.gsub('_', ' ')} #{association}" do
|
88
|
+
expect(subject).to respond_to(association)
|
89
|
+
case assoc_type
|
90
|
+
when :belongs_to
|
91
|
+
%W(#{association}= #{association}_id #{association}_id=).each do |method|
|
92
|
+
expect(subject).to respond_to(method)
|
93
|
+
end
|
94
|
+
when :has_many
|
95
|
+
expect(subject).to respond_to("#{association.to_s.singularize}_ids")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_length_validation(key, value)
|
102
|
+
case key
|
103
|
+
when :in, :within then ['is out of the length range', value.max + 1]
|
104
|
+
when :is, :minimum then ["is #{key == :is ? 'invalid' : 'too short'}", value - 1]
|
105
|
+
when :maximum then ['is too long', value + 1]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_type_str(options)
|
110
|
+
if !options.nil? && options[:type]
|
111
|
+
" (%s%s%s)" % [
|
112
|
+
('Enumerated ' if options[:enum]),
|
113
|
+
options[:type],
|
114
|
+
(", default: #{options[:default]}" if options[:default])
|
115
|
+
]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def scoped_validation?(validation)
|
120
|
+
validation.is_a?(Hash) && (%i(scope mock) - validation.keys).empty?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SmartRspec
|
2
|
+
module Support
|
3
|
+
module Expectations
|
4
|
+
def assert_attr_type(attr, type, other)
|
5
|
+
assert_type = type == :Boolean ? be_boolean : be_kind_of(Kernel.const_get(type))
|
6
|
+
expect(subject.send(attr)).to assert_type
|
7
|
+
end
|
8
|
+
|
9
|
+
def assert_validation(attr, value = nil, mock = nil)
|
10
|
+
mock ||= subject
|
11
|
+
mock.send("#{attr}=", value)
|
12
|
+
|
13
|
+
expect(mock).not_to be_valid
|
14
|
+
expect(mock).to have_error_on(attr)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SmartRspec
|
2
|
+
module Support
|
3
|
+
module Regexes
|
4
|
+
def build_regex(type, *args)
|
5
|
+
type = type.to_sym unless type.is_a? Symbol
|
6
|
+
case type
|
7
|
+
when :email
|
8
|
+
/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
9
|
+
when :image
|
10
|
+
build_img_regex(args.flatten)
|
11
|
+
else
|
12
|
+
build_uri_regex[type]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private_class_method
|
17
|
+
|
18
|
+
def build_img_regex(exts = [])
|
19
|
+
exts = [exts].flatten unless exts.is_a?(Array)
|
20
|
+
if exts.nil? || exts.empty?
|
21
|
+
exts = %w(jpg jpeg png gif)
|
22
|
+
elsif exts.include?(:jpg) && !exts.include?(:jpeg)
|
23
|
+
exts.push :jpeg
|
24
|
+
end
|
25
|
+
%r{(^http{1}[s]?://([w]{3}\.)?.+\.(#{exts.join('|')})(\?.+)?$)}i
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_uri_regex
|
29
|
+
{
|
30
|
+
uri: %r{^(
|
31
|
+
(((ht|f)tp[s]?://)|([a-z0-9]+\.))+
|
32
|
+
(?<!@)
|
33
|
+
([a-z0-9\_\-]+)
|
34
|
+
(\.[a-z]+)+
|
35
|
+
([\?/\:][a-z0-9_=%&@\?\./\-\:\#\(\)]+)?
|
36
|
+
/?
|
37
|
+
)$}ix,
|
38
|
+
protocol: /((ht|f)tp[s]?)/i
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/lib/smart_rspec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'smart_rspec/macros'
|
3
|
+
require 'smart_rspec/matchers'
|
4
|
+
require 'smart_rspec/support/expectations'
|
5
|
+
|
6
|
+
include SmartRspec::Matchers
|
7
|
+
|
8
|
+
module SmartRspec
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
include SmartRspec::Support::Expectations
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
include SmartRspec::Macros
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
data/smart_rspec.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'smart_rspec/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'smart_rspec'
|
8
|
+
spec.version = SmartRspec::VERSION
|
9
|
+
spec.authors = ['Tiago Guedes']
|
10
|
+
spec.email = ['tiagopog@gmail.com']
|
11
|
+
spec.summary = %q{Macros and matchers to make your RSpec tests even more amazing.}
|
12
|
+
spec.description = %q{Collection of useful macros and matchers for RSpec tests of models/controllers in Rails-based apps.}
|
13
|
+
spec.homepage = 'https://github.com/tiagopog/smart_rspec'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.1'
|
22
|
+
spec.add_runtime_dependency 'rspec-collection_matchers', '~> 1.1', '>= 1.1.2'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
25
|
+
spec.add_development_dependency 'faker', '~> 1.4'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
28
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'smart_rspec/support/regexes'
|
2
|
+
|
3
|
+
module Factories
|
4
|
+
class User
|
5
|
+
include SmartRspec::Support::Regexes
|
6
|
+
|
7
|
+
@@last_id = 0
|
8
|
+
@@collection = []
|
9
|
+
@@error_message = {
|
10
|
+
blank: "can't be blank",
|
11
|
+
exclusion: "can't use a reserved value",
|
12
|
+
format: "doesn't match the required pattern",
|
13
|
+
inclusion: 'value not included',
|
14
|
+
too_big: "can't be greater than 80 chars",
|
15
|
+
uniqueness: 'must be unique within the given scope'
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_accessor :email, :system, :system_id, :project, :project_id,
|
19
|
+
:name, :username, :is_admin, :score, :admin, :father,
|
20
|
+
:mother, :articles, :rates
|
21
|
+
|
22
|
+
attr_reader :id, :errors
|
23
|
+
|
24
|
+
def initialize(attrs = {})
|
25
|
+
attrs.each { |key, value| self.send("#{key}=", value) }
|
26
|
+
set_defaults
|
27
|
+
@@collection << self
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
attr_reader :collection
|
32
|
+
end
|
33
|
+
|
34
|
+
def locale
|
35
|
+
@locale.to_s unless @locale.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def locale=(locale)
|
39
|
+
%i(en pt).include?(locale) && @locale = locale
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?
|
43
|
+
%w(email father locale name username).each { |e| send("check_#{e}") }
|
44
|
+
@errors.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def check_email
|
50
|
+
if !email || (email && email !~ build_regex(:email))
|
51
|
+
@errors.merge!({ email: @@error_message[:blank] })
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_father
|
56
|
+
if father && father !~ /foo/
|
57
|
+
@errors.merge!({ father: @@error_message[:format] })
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_locale
|
62
|
+
unless %i(en pt).include?(locale)
|
63
|
+
@errors.merge!({ locale: @@error_message[:inclusion] })
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_name
|
68
|
+
if !name || (name && name.length > 80)
|
69
|
+
@errors.merge!({ name: @@error_message[(name ? :too_big : :blank)] })
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_username
|
74
|
+
other_user = @@collection.select { |e| e.name == name && e.username == username && e.id != id }.first
|
75
|
+
if username && (other_user || %w(foo bar).include?(username))
|
76
|
+
@errors.merge!({ username: @@error_message[other_user ? :uniqueness : :exclusion] })
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_defaults
|
81
|
+
@@last_id = @id = @@last_id + 1
|
82
|
+
{ errors: {}, is_admin: false, score: 0, locale: :en }.each do |key, value|
|
83
|
+
eval "@#{key} ||= #{value.inspect}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmartRspec::Macros do
|
4
|
+
include SmartRspec
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
attrs = {
|
8
|
+
email: Faker::Internet.email,
|
9
|
+
name: Faker::Name.name,
|
10
|
+
username: Faker::Internet.user_name
|
11
|
+
}
|
12
|
+
@user = User.new(attrs)
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { @user }
|
16
|
+
|
17
|
+
describe '#belongs_to' do
|
18
|
+
context 'when it receives a single arg' do
|
19
|
+
belongs_to :system
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when it receives multiple args' do
|
23
|
+
belongs_to :system, :project
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#has_attributes' do
|
28
|
+
context 'when it receives a single arg' do
|
29
|
+
has_attributes :email, :name, :username, type: :String
|
30
|
+
has_attributes :is_admin, type: :Boolean
|
31
|
+
has_attributes :score, type: :Integer, default: 0
|
32
|
+
has_attributes :locale, type: :String, enum: %i(en pt), default: 'en'
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when it receives multiple args' do
|
36
|
+
has_attributes :name, :username, type: :String
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#has_one' do
|
41
|
+
context 'when it receives a single arg' do
|
42
|
+
has_one :admin
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when it receives multiple args' do
|
46
|
+
has_one :admin, :father, :mother
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#has_many' do
|
51
|
+
context 'when it receives a single arg' do
|
52
|
+
has_one :articles
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when it receives multiple args' do
|
56
|
+
has_one :articles, :rates
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#fails_validation_of' do
|
61
|
+
context 'when it receives a single arg' do
|
62
|
+
user = User.new(email: Faker::Internet.email)
|
63
|
+
|
64
|
+
fails_validation_of :email, presence: true, email: true
|
65
|
+
fails_validation_of :name, length: { maximum: 80 }
|
66
|
+
fails_validation_of :username, uniqueness: { scope: :name, mock: user }, exclusion: { in: %w(foo bar) }
|
67
|
+
fails_validation_of :locale, inclusion: { in: %w(en pt) }
|
68
|
+
fails_validation_of :father, format: { with: /foo/, mock: 'bar' }
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when it receives multiple args' do
|
72
|
+
fails_validation_of :email, :name, presence: true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'SmartRspec Matchers' do
|
4
|
+
describe '#be_ascending' do
|
5
|
+
context 'when valid' do
|
6
|
+
it { expect([1, 2, 3, 4]).to be_ascending }
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when invalid' do
|
10
|
+
it { expect([1, 4, 2, 3]).not_to be_ascending }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#be_boolean' do
|
15
|
+
context 'when valid' do
|
16
|
+
it { expect(true).to be_boolean }
|
17
|
+
it { expect(false).to be_boolean }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when invalid' do
|
21
|
+
it { expect('true').not_to be_boolean }
|
22
|
+
it { expect(1).not_to be_boolean }
|
23
|
+
it { expect(%w(foo bar)).not_to be_boolean }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#be_descending' do
|
28
|
+
context 'when valid' do
|
29
|
+
it { expect([4, 3, 2, 1]).to be_descending }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when invalid' do
|
33
|
+
it { expect([1, 2, 3, 4]).not_to be_descending }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#be_email' do
|
38
|
+
context 'when valid' do
|
39
|
+
it { expect(Faker::Internet.email).to be_email }
|
40
|
+
it { expect('tiagopog@gmail.com').to be_email }
|
41
|
+
it { expect('foo@bar.com.br').to be_email }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when invalid' do
|
45
|
+
it { expect('foo@bar').not_to be_email }
|
46
|
+
it { expect('foo@').not_to be_email }
|
47
|
+
it { expect('@bar').not_to be_email }
|
48
|
+
it { expect('@bar.com').not_to be_email }
|
49
|
+
it { expect('foo bar@bar.com').not_to be_email }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#be_url' do
|
54
|
+
context 'when valid' do
|
55
|
+
it { expect(Faker::Internet.url).to be_url }
|
56
|
+
it { expect('http://adtangerine.com').to be_url }
|
57
|
+
it { expect('http://www.facebook.com').to be_url }
|
58
|
+
it { expect('www.twitflink.com').to be_url }
|
59
|
+
it { expect('google.com.br').to be_url }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when invalid' do
|
63
|
+
it { expect('foobar.bar').not_to be_url }
|
64
|
+
it { expect('foobar').not_to be_url }
|
65
|
+
it { expect('foo bar.com.br').not_to be_url }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#be_image_url' do
|
70
|
+
context 'when valid' do
|
71
|
+
it { expect(Faker::Company.logo).to be_image_url }
|
72
|
+
it { expect('http://foobar.com/foo.jpg').to be_image_url }
|
73
|
+
it { expect('http://foobar.com/foo.jpg').to be_image_url(:jpg) }
|
74
|
+
it { expect('http://foobar.com/foo.gif').to be_image_url(:gif) }
|
75
|
+
it { expect('http://foobar.com/foo.png').to be_image_url(:png) }
|
76
|
+
it { expect('http://foobar.com/foo.png').to be_image_url(%i(jpg png)) }
|
77
|
+
it { expect('http://foobar.com/foo/bar?image=foo.jpg').to be_image_url }
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when invalid' do
|
81
|
+
it { expect('http://foobar.com').not_to be_image_url }
|
82
|
+
it { expect('http://foobar.com/foo.jpg').not_to be_image_url(:gif) }
|
83
|
+
it { expect('http://foobar.com/foo.gif').not_to be_image_url(:png) }
|
84
|
+
it { expect('http://foobar.com/foo.png').not_to be_image_url(:jpg) }
|
85
|
+
it { expect('http://foobar.com/foo.gif').not_to be_image_url(%i(jpg png)) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#have' do
|
90
|
+
context 'when valid' do
|
91
|
+
it { expect([1]).to have(1).item }
|
92
|
+
it { expect(%w(foo bar)).to have(2).items }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when invalid' do
|
96
|
+
it { expect([1]).not_to have(2).items }
|
97
|
+
it { expect(%w(foo bar)).not_to have(1).item }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#have_at_least' do
|
102
|
+
context 'when valid' do
|
103
|
+
it { expect(%w(foo bar foobar)).to have_at_least(3).items }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#have_at_most' do
|
108
|
+
context 'when valid' do
|
109
|
+
it { expect(%w(foo bar foobar)).to have_at_most(3).items }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#have_error_on' do
|
114
|
+
subject(:mock) { User.new(email: nil) }
|
115
|
+
|
116
|
+
context 'when valid' do
|
117
|
+
it do
|
118
|
+
mock.valid?
|
119
|
+
expect(mock).to have_error_on(:email)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'when invalid' do
|
124
|
+
it do
|
125
|
+
mock.email = Faker::Internet.email
|
126
|
+
mock.valid?
|
127
|
+
|
128
|
+
expect(mock).not_to have_error_on(:email)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#include_items' do
|
134
|
+
context 'when valid' do
|
135
|
+
it { expect(%w(foo bar foobar)).to include_items(%w(foo bar foobar)) }
|
136
|
+
it { expect([1, 'foo', ['bar']]).to include_items([1, 'foo', ['bar']]) }
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when invalid' do
|
140
|
+
it { expect(%w(foo bar foobar)).not_to include_items(%w(lorem)) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smart_rspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tiago Guedes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-collection_matchers
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.1'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.1.2
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.1'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.1.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.7'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.7'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: faker
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.4'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.4'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '10.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '10.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.2'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.2'
|
103
|
+
description: Collection of useful macros and matchers for RSpec tests of models/controllers
|
104
|
+
in Rails-based apps.
|
105
|
+
email:
|
106
|
+
- tiagopog@gmail.com
|
107
|
+
executables: []
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- ".gitignore"
|
112
|
+
- ".rspec"
|
113
|
+
- Gemfile
|
114
|
+
- LICENSE.txt
|
115
|
+
- README.md
|
116
|
+
- Rakefile
|
117
|
+
- lib/smart_rspec.rb
|
118
|
+
- lib/smart_rspec/macros.rb
|
119
|
+
- lib/smart_rspec/matchers.rb
|
120
|
+
- lib/smart_rspec/support/assertions.rb
|
121
|
+
- lib/smart_rspec/support/expectations.rb
|
122
|
+
- lib/smart_rspec/support/regexes.rb
|
123
|
+
- lib/smart_rspec/version.rb
|
124
|
+
- smart_rspec.gemspec
|
125
|
+
- spec/factories/user.rb
|
126
|
+
- spec/smart_rspec/macros_spec.rb
|
127
|
+
- spec/smart_rspec/matchers_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
homepage: https://github.com/tiagopog/smart_rspec
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.4.6
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Macros and matchers to make your RSpec tests even more amazing.
|
153
|
+
test_files:
|
154
|
+
- spec/factories/user.rb
|
155
|
+
- spec/smart_rspec/macros_spec.rb
|
156
|
+
- spec/smart_rspec/matchers_spec.rb
|
157
|
+
- spec/spec_helper.rb
|