active_record_block_matchers 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0d4300a69d179e6d1179829142e265a79dfca842
4
- data.tar.gz: bd6f73e84dc9d9530d6f7771f478e413b68ba91f
3
+ metadata.gz: 56549819eb77b8a9e445755d2ed4d66c8a7c78d7
4
+ data.tar.gz: 5eae4e2fbe09eff5486f783ff296b437b92c84bd
5
5
  SHA512:
6
- metadata.gz: 764d8f289679718c85409795111582ed667c7c6e0844cace7a402829f4dace7e63b7e94b12b70e0761e4bb80b99acae71ce6ae96e57348a3bf27b021c365064c
7
- data.tar.gz: 700dbeb4e9890cb99da19301725c2fbd723edaa5e8aabf127e7157429dc5862314339e3543ad00f896656cfece516b3a838cfb55086d898239fde578dd9b780f
6
+ metadata.gz: a8702eb8afc69876c8b15bb624124de2aae8c0735e8c05a9f6faf67fc7cf470c3163e2332c1fee2faf6c97dec8d7a3d8695e74e0264f550eaa465e87ab210316
7
+ data.tar.gz: 4f957401eac7b5b596bf04dbe6a402f2ba0c88d12facf8e7f017c29ba1bee72b410de8834dc1f3e4c2ac9849664f761aba6b77ce88122b946320d1967965363b
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
- --format documentation
1
+ --format progress
2
2
  --color
3
+ --order random
data/README.md CHANGED
@@ -21,29 +21,51 @@ Or install it yourself as:
21
21
 
22
22
  $ gem install active_record_block_matchers
23
23
 
24
- ## Custom Matchers
24
+ ## Quick Examples
25
25
 
26
- #### `create_a_new`
26
+ ```
27
+ expect {
28
+ post :create, user: { username: "bob", password: "BlueSteel45" }
29
+ }.to create_a(User)
30
+ .with_attributes(username: "bob")
31
+ .which {|bob| expect(AuthLibrary.authenticate("bob", "BlueSteel45")).to eq bob }
32
+
33
+ expect {
34
+ post :create, user: { username: "bob", password: "BlueSteel45" }
35
+ }.to create(User => 1, Profile => 1)
36
+ .with_attributes(
37
+ User => [{username: "bob"}],
38
+ Profile => [{avatar_url: Avatar.default_avatar_url}],
39
+ ).which { |new_records_hash|
40
+ new_user = new_records_hash[User].first
41
+ new_profile = new_records_hash[Profile].first
42
+ expect(new_user.profile).to eq new_profile
43
+ }
44
+ ```
45
+
46
+ ## Detailed Examples
47
+
48
+ #### `create_a`
27
49
 
28
- aliases: `create_a`, `create_an`
50
+ aliases: `create_an`, `create_a_new`
29
51
 
30
52
  Example:
31
53
 
32
54
  ```ruby
33
- expect { User.create! }.to create_a_new(User)
55
+ expect { User.create! }.to create_a(User)
34
56
  ```
35
57
 
36
58
  This can be very useful for controller tests:
37
59
 
38
60
  ```ruby
39
- expect { post :create, user: user_params }.to create_a_new(User)
61
+ expect { post :create, user: user_params }.to create_a(User)
40
62
  ```
41
63
 
42
64
  You can chain `.with_attributes` as well to define a list of values you expect the new object to have. This works with both database attributes and computed values.
43
65
 
44
66
  ```ruby
45
67
  expect { User.create!(username: "bob") }
46
- .to create_a_new(User)
68
+ .to create_a(User)
47
69
  .with_attributes(username: "bob")
48
70
  ```
49
71
 
@@ -51,7 +73,7 @@ This is a great way to test ActiveReocrd hooks on your model. For example, if y
51
73
 
52
74
  ```ruby
53
75
  expect { User.create!(username: "BOB") }
54
- .to create_a_new(User)
76
+ .to create_a(User)
55
77
  .with_attributes(username: "bob")
56
78
  ```
57
79
 
@@ -59,7 +81,7 @@ You can even use RSpec's [composable matchers][1]:
59
81
 
60
82
  ```ruby
61
83
  expect { User.create!(username: "bob") }
62
- .to create_a_new(User)
84
+ .to create_a(User)
63
85
  .with_attributes(username: a_string_starting_with("b"))
64
86
  ```
65
87
 
@@ -67,17 +89,74 @@ If you need to make assertions about things other than attribute equality, you c
67
89
 
68
90
  ```ruby
69
91
  expect { User.create!(username: "BOB", password: "BlueSteel45") }
70
- .to create_a_new(User)
92
+ .to create_a(User)
71
93
  .which { |user|
72
94
  expect(user.encrypted_password).to be_present
73
95
  expect(AuthLibrary.authenticate("bob", "BlueSteel45")).to eq user
74
96
  }
75
97
  ```
76
98
 
77
- **Gotcha Warning:** Be careful about your block syntax when chaining `.which` in your tests. If you write the above example with a `do...end`, the example will parse like this: `expect {...}.to(create_a_new(User).which) do |user| ... end`, so your block will not execute, and it may appear that your test is passing, when it is not.
99
+ **Gotcha Warning:** Be careful about your block syntax when chaining `.which` in your tests. If you write the above example with a `do...end`, the example will parse like this: `expect {...}.to(create_a(User).which) do |user| ... end`, so your block will not execute, and it may appear that your test is passing, when it is not.
78
100
 
79
101
  This matcher relies on a `created_at` column existing on the given model class. The name of this column can be configured via `ActiveRecordBlockMatchers::Config.created_at_column_name = "your_column_name"`
80
102
 
103
+ #### `create`
104
+
105
+ aliases: `create_records`
106
+
107
+ Example:
108
+
109
+ ```ruby
110
+ expect { User.create!; User.create!; Profile.create! }
111
+ .to create(User => 2, Profile => 1)
112
+ ```
113
+
114
+ Just like the other matcher, you can chain `with_attributes` and `which` to assert about the particulars of the records:
115
+
116
+ ```ruby
117
+ expect { UserService.sign_up!(username: "bob", password: "BlueSteel45") }
118
+ .to create(User => 1, Profile => 1)
119
+ .with_attributes(
120
+ User => [{username: "bob"}],
121
+ Profile => [{avatar_url: Avatar.default_avatar_url}]
122
+ ).which { |records|
123
+ # records is a hash with model classes for keys and the new records for values
124
+ new_user = records[User].first
125
+ new_profile = records[Profile].first
126
+ expect(AuthLibrary.authenticate("bob", "BlueSteel45")).to eq new_user
127
+ expect(new_user.profile).to eq new_profile
128
+ }
129
+ ```
130
+
131
+ As noted, the `which` block yields a hash containing the new records whose counts were specified.
132
+
133
+ Order doesn't matter for the attributes specified in `with_attributes`, but you must provide an attribute hash for every record that was created. This means, if you expect the block to create, say 2 User records, you must provide an attributes hash for each new User record:
134
+
135
+ ```ruby
136
+ # This is correct:
137
+ expect { User.create!(username: "bob"); User.create!(username: "rhonda") }
138
+ .to create(User => 2)
139
+ .with_attributes(
140
+ User => [{username: "rhonda"}, {username: "bob"}]
141
+ )
142
+
143
+ # This will raise an error:
144
+ expect { User.create!(username: "bob"); User.create!(username: "rhonda") }
145
+ .to create(User => 2)
146
+ .with_attributes(
147
+ User => [{username: "rhonda"}]
148
+ )
149
+
150
+ # But this is totally fine if you really need a workaround:
151
+ # Just put the empty hashes last
152
+ expect { User.create!(username: "bob"); User.create!(username: "rhonda") }
153
+ .to create(User => 2)
154
+ .with_attributes(
155
+ User => [{username: "rhonda"}, {}]
156
+ )
157
+ ```
158
+
159
+
81
160
  ## Development
82
161
 
83
162
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,9 @@
1
+ class CreateDogs < ActiveRecord::Migration
2
+ def change
3
+ create_table :dogs do |t|
4
+ t.string :name
5
+ t.string :breed
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
data/db/schema.rb CHANGED
@@ -11,7 +11,14 @@
11
11
  #
12
12
  # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(:version => 20150225014908) do
14
+ ActiveRecord::Schema.define(:version => 20151017231107) do
15
+
16
+ create_table "dogs", :force => true do |t|
17
+ t.string "name"
18
+ t.string "breed"
19
+ t.datetime "created_at", :null => false
20
+ t.datetime "updated_at", :null => false
21
+ end
15
22
 
16
23
  create_table "people", :force => true do |t|
17
24
  t.string "first_name"
@@ -1,8 +1,7 @@
1
- require "active_record_block_matchers/version"
2
1
  require "rspec/expectations"
3
2
  require "active_record"
4
- require "active_record_block_matchers/config"
5
- require "active_record_block_matchers/create_a_matcher"
3
+
4
+ Dir[File.dirname(__FILE__) + "/active_record_block_matchers/**/*.rb"].each {|file| require file }
6
5
 
7
6
  module ActiveRecordBlockMatchers
8
7
  end
@@ -0,0 +1,117 @@
1
+ RSpec::Matchers.define :create_records do |record_counts|
2
+ include ActiveSupport::Inflector
3
+
4
+ supports_block_expectations
5
+
6
+ description do
7
+ counts_strs = record_counts.map { |klass, count| count_str(klass, count) }
8
+ "create #{counts_strs.join(", ")}"
9
+ end
10
+
11
+ chain(:with_attributes) do |attributes|
12
+ if mismatch=attributes.find {|klass, hashes| hashes.size != record_counts[klass]}
13
+ mismatched_class, hashes = mismatch
14
+ raise ArgumentError, "Specified the block should create #{record_counts[mismatched_class]} #{mismatched_class}, but provided #{hashes.size} #{mismatched_class} attribute specifications"
15
+ end
16
+ @expected_attributes = attributes
17
+ end
18
+
19
+ chain(:which) do |&block|
20
+ @which_block = block
21
+ end
22
+
23
+ match do |block|
24
+ time_before = Time.current
25
+
26
+ block.call
27
+
28
+ @new_records =
29
+ record_counts.keys.each_with_object({}) do |klass, new_records|
30
+ column_name = ActiveRecordBlockMatchers::Config.created_at_column_name
31
+ new_records[klass] = klass.where("#{column_name} > ?", time_before).to_a
32
+ end
33
+
34
+ @incorrect_counts =
35
+ @new_records.each_with_object({}) do |(klass, new_records), incorrect|
36
+ actual_count = new_records.count
37
+ expected_count = record_counts[klass]
38
+ if actual_count != expected_count
39
+ incorrect[klass] = { expected: expected_count, actual: actual_count }
40
+ end
41
+ end
42
+
43
+ return false if @incorrect_counts.any?
44
+
45
+ if @expected_attributes
46
+ @matched_records = Hash.new {|hash, key| hash[key] = []}
47
+ @all_attributes = Hash.new {|hash, key| hash[key] = []}
48
+ @incorrect_attributes =
49
+ @expected_attributes.each_with_object(Hash.new {|hash, key| hash[key] = []}) do |(klass, expected_attributes), incorrect|
50
+ @all_attributes[klass] = expected_attributes.map(&:keys).flatten.uniq
51
+ expected_attributes.each do |expected_attrs|
52
+ matched_record = (@new_records.fetch(klass) - @matched_records[klass]).find do |record|
53
+ expected_attrs.all? {|k,v| values_match?(v, record.public_send(k))}
54
+ end
55
+ if matched_record
56
+ @matched_records[klass] << matched_record
57
+ else
58
+ incorrect[klass] << expected_attrs
59
+ end
60
+ end
61
+ end
62
+ @unmatched_records = @matched_records.map {|klass, records| [klass, @new_records[klass] - records]}.to_h.reject {|k,v| v.empty?}
63
+ return false if @incorrect_attributes.any?
64
+ end
65
+
66
+
67
+ begin
68
+ @which_block && @which_block.call(@new_records)
69
+ rescue RSpec::Expectations::ExpectationNotMetError => e
70
+ @which_failure = e
71
+ end
72
+
73
+ @which_failure.nil?
74
+ end
75
+
76
+ failure_message do
77
+ if @incorrect_counts.present?
78
+ @incorrect_counts.map do |klass, counts|
79
+ "The block should have created #{count_str(klass, counts[:expected])}, but created #{counts[:actual]}."
80
+ end.join(" ")
81
+ elsif @incorrect_attributes.present?
82
+ "The block should have created:\n" +
83
+ @expected_attributes.map do |klass, attrs|
84
+ " #{attrs.count} #{klass} with these attributes:\n" +
85
+ attrs.map{|a| " #{a.inspect}"}.join("\n")
86
+ end.join("\n") +
87
+ "\nDiff:" +
88
+ @incorrect_attributes.map do |klass, attrs|
89
+ "\n Missing #{attrs.count} #{klass} with these attributes:\n" +
90
+ attrs.map{|a| " #{a.inspect}"}.join("\n")
91
+ end.join("\n") +
92
+ @unmatched_records.map do |klass, records|
93
+ "\n Extra #{records.count} #{klass} with these attributes:\n" +
94
+ records.map do |r|
95
+ attrs = @all_attributes[klass].each_with_object({}) {|attr, attrs| attrs[attr] = r.public_send(attr)}
96
+ " #{attrs.inspect}"
97
+ end.join("\n")
98
+ end.join("\n")
99
+ elsif @which_failure
100
+ @which_failure
101
+ else
102
+ "Unknown error"
103
+ end
104
+ end
105
+
106
+ failure_message_when_negated do
107
+ record_counts.map do |klass, expected_count|
108
+ "The block should not have created #{count_str(klass, expected_count)}, but created #{expected_count}."
109
+ end.join(" ")
110
+ end
111
+
112
+ def count_str(klass, count)
113
+ "#{count} #{klass.name.pluralize(count)}"
114
+ end
115
+ end
116
+
117
+ RSpec::Matchers.alias_matcher :create, :create_records
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordBlockMatchers
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_block_matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Wallace
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-11-19 00:00:00.000000000 Z
11
+ date: 2016-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -142,10 +142,12 @@ files:
142
142
  - bin/setup
143
143
  - db/config.yml
144
144
  - db/migrate/20150225014908_create_people.rb
145
+ - db/migrate/20151017231107_create_dogs.rb
145
146
  - db/schema.rb
146
147
  - lib/active_record_block_matchers.rb
147
148
  - lib/active_record_block_matchers/config.rb
148
- - lib/active_record_block_matchers/create_a_matcher.rb
149
+ - lib/active_record_block_matchers/create_a_new_matcher.rb
150
+ - lib/active_record_block_matchers/create_records_matcher.rb
149
151
  - lib/active_record_block_matchers/version.rb
150
152
  homepage: https://github.com/nwallace/active_record_block_matchers
151
153
  licenses:
@@ -167,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
169
  version: '0'
168
170
  requirements: []
169
171
  rubyforge_project:
170
- rubygems_version: 2.4.3
172
+ rubygems_version: 2.5.1
171
173
  signing_key:
172
174
  specification_version: 4
173
175
  summary: Additional RSpec custom matchers for ActiveRecord