active_record_block_matchers 0.1.3 → 0.2.0

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.
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