Skip to content

Commit e6af276

Browse files
committed
[Fix #1396] Add new cop assert_changes_second_argument
1 parent fecead8 commit e6af276

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#1396](https://github.com/rubocop/rubocop-rails/issues/1396): Add cop to prevent use of assert_changes with non-string second arguments. ([@joseph-carino][])

config/default.yml

+7
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ Rails/ArelStar:
222222
SafeAutoCorrect: false
223223
VersionAdded: '2.9'
224224

225+
Rails/AssertChangesSecondArgument:
226+
Description: 'Do not use `assert_changes` with a non-string second argument.'
227+
Enabled: true
228+
VersionAdded: '2.28'
229+
Include:
230+
- '**/test/**/*'
231+
225232
Rails/AssertNot:
226233
Description: 'Use `assert_not` instead of `assert !`.'
227234
Enabled: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Checks misuse of the second argument to ActiveSupport `assert_changes` method
7+
#
8+
# `assert_changes`'s second argument is the failure message emitted when the
9+
# first argument (expression) is unchanged in the block.
10+
#
11+
# A common mistake is to use `assert_changes` with the expected change
12+
# value delta as the second argument.
13+
# In that case `assert_changes` will validate only that the expression changes,
14+
# not that it changes by a specific value.
15+
#
16+
# Users should provide the 'from' and 'to' parameters,
17+
# or use `assert_difference` instead, which does support deltas.
18+
#
19+
# @example
20+
#
21+
# # bad
22+
# assert_changes -> { @value }, 1 do
23+
# @value += 1
24+
# end
25+
#
26+
# # good
27+
# assert_changes -> { @value }, from: 0, to: 1 do
28+
# @value += 1
29+
# end
30+
# assert_changes -> { @value }, "Value should change" do
31+
# @value += 1
32+
# end
33+
# assert_difference -> { @value }, 1 do
34+
# @value += 1
35+
# end
36+
#
37+
class AssertChangesSecondArgument < Base
38+
extend AutoCorrector
39+
40+
MSG = 'Use assert_changes to assert when an expression changes from and to specific values. ' \
41+
'Use assert_difference instead to assert when an expression changes by a specific delta. ' \
42+
'The second argument to assert_changes is the message emitted on assert failure.'
43+
44+
def on_send(node)
45+
return if node.receiver || !node.method?(:assert_changes)
46+
return if node.arguments[1].nil?
47+
48+
return unless offense?(node.arguments[1])
49+
50+
add_offense(node.loc.selector) do |corrector|
51+
corrector.replace(node.loc.selector, 'assert_difference')
52+
end
53+
end
54+
55+
private
56+
57+
def offense?(arg)
58+
!arg.hash_type? && !arg.str_type? && !arg.dstr_type? && !arg.sym_type? && !arg.dsym_type? && !arg.variable?
59+
end
60+
end
61+
end
62+
end
63+
end

lib/rubocop/cop/rails_cops.rb

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
require_relative 'rails/application_mailer'
2626
require_relative 'rails/application_record'
2727
require_relative 'rails/arel_star'
28+
require_relative 'rails/assert_changes_second_argument'
2829
require_relative 'rails/assert_not'
2930
require_relative 'rails/attribute_default_block_value'
3031
require_relative 'rails/belongs_to'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe(RuboCop::Cop::Rails::AssertChangesSecondArgument, :config) do
4+
let(:message) do
5+
'Use assert_changes to assert when an expression changes from and to specific values. ' \
6+
'Use assert_difference instead to assert when an expression changes by a specific delta. ' \
7+
'The second argument to assert_changes is the message emitted on assert failure.'
8+
end
9+
10+
describe('offenses') do
11+
it 'adds offense when the second positional argument is an integer' do
12+
expect_offense(<<~RUBY)
13+
assert_changes @value, -1 do
14+
^^^^^^^^^^^^^^ #{message}
15+
@value += 1
16+
end
17+
RUBY
18+
end
19+
20+
it 'adds offense when the second positional argument is a float' do
21+
expect_offense(<<~RUBY)
22+
assert_changes @value, -1.0 do
23+
^^^^^^^^^^^^^^ #{message}
24+
@value += 1
25+
end
26+
RUBY
27+
end
28+
29+
it 'does not add offense when the second argument is a string' do
30+
expect_no_offenses(<<~RUBY)
31+
assert_changes @value, "Value should change" do
32+
@value += 1
33+
end
34+
RUBY
35+
end
36+
37+
it 'does not add offense when the second argument is an interpolated string' do
38+
expect_no_offenses(<<~RUBY)
39+
assert_changes @value, "\#{thing} should change" do
40+
@value += 1
41+
end
42+
RUBY
43+
end
44+
45+
it 'does not add offense when the second argument is a symbol' do
46+
expect_no_offenses(<<~RUBY)
47+
assert_changes @value, :should_change do
48+
@value += 1
49+
end
50+
RUBY
51+
end
52+
53+
it 'does not add offense when the second argument is an interpolated symbol' do
54+
expect_no_offenses(<<~RUBY)
55+
assert_changes @value, :"\#{thing}_should_change" do
56+
@value += 1
57+
end
58+
RUBY
59+
end
60+
61+
it 'does not add offense when the second argument is a variable' do
62+
expect_no_offenses(<<~RUBY)
63+
message = "Value should change"
64+
assert_changes @value, message do
65+
@value += 1
66+
end
67+
RUBY
68+
end
69+
70+
it 'does not add offense when there is only one argument' do
71+
expect_no_offenses(<<~RUBY)
72+
assert_changes @value do
73+
@value += 1
74+
end
75+
RUBY
76+
end
77+
78+
it 'does not add offense when there is only one positional argument' do
79+
expect_no_offenses(<<~RUBY)
80+
assert_changes @value, from: 0 do
81+
@value += 1
82+
end
83+
RUBY
84+
end
85+
end
86+
87+
describe('autocorrect') do
88+
it 'autocorrects method from assert_changes to assert_difference' do
89+
source = <<-RUBY
90+
assert_changes @value, -1.0 do
91+
@value += 1
92+
end
93+
RUBY
94+
95+
corrected_source = <<-RUBY
96+
assert_difference @value, -1.0 do
97+
@value += 1
98+
end
99+
RUBY
100+
101+
expect(autocorrect_source(source)).to(eq(corrected_source))
102+
end
103+
end
104+
end

0 commit comments

Comments
 (0)