8000 Add new cop `RSpec/RedundantPending` by ydah · Pull Request #2125 · rubocop/rubocop-rspec · GitHub
[go: up one dir, main page]

Skip to content

Conversation

ydah
Copy link
Member
@ydah ydah commented Oct 5, 2025

This cop detects redundant pending or skip calls inside examples that are already marked as skipped. When an example is skipped using xit, xspecify, xexample, or skip/pending metadata, adding a pending or skip call inside the example body is redundant and creates unnecessary duplication.

It's common to see code like this:

xspecify do
  pending 'Need to upgrade to the latest HTTP gem version'
  expect(pinger.call).to eq(204)
end

xit 'answers success status' do
  pending 'Need to upgrade HTTP gem before this will work again'
  expect(pinger.call).to eq(200)
end

In these cases:

  • The example is already skipped via xspecify/xit
  • The pending call inside is redundant and never executed
  • This creates confusion about where the skip/pending is actually applied
  • The reason message in pending is not displayed (the example is already skipped at method level)

This cop flags such redundant calls and suggests either:

  1. Remove the redundant pending/skip call entirely, OR
  2. Use a regular example method (it, specify) if the pending call is actually needed

The cop detects redundancy in the following cases:

  1. Skipped example methods:

    • xit, xspecify, xexample
  2. Skip/pending metadata:

    • Symbol metadata: it 'test', :skip do
    • Symbol metadata: it 'test', :pending do
    • Hash metadata: it 'test', skip: true do
    • Hash metadata: it 'test', skip: 'reason' do
    • Hash metadata: it 'test', pending: 'reason' do
  3. Both pending and skip calls:

    • Detects both pending 'reason' and skip 'reason'

The cop does NOT flag these legitimate uses:

  1. Regular examples with pending/skip: ruby it 'does something' do pending 'not yet implemented' expect(something).to be_truthy end

  2. Conditional skip/pending: ruby xit 'does something' do setup_something skip 'setup failed' if setup_failed? # Not first statement expect(something).to be_truthy end

  3. Metadata with false value: ruby it 'does something', skip: false do pending 'conditional pending' expect(something).to be_truthy end

  • Checks only the first statement in the example body

  • Supports both regular blocks (do...end) and numblocks

  • Handles examples with single or multiple statements

  • Handles one-liner syntax: xit('test') { pending 'reason' }

  • Reduces confusion about where examples are actually skipped

  • Eliminates redundant code

  • Makes skip/pending reasons visible (use metadata instead of body)

  • Improves test suite maintainability


Before submitting the PR make sure the following are checked:

  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Updated documentation.
  • Added an entry to the CHANGELOG.md if the new code introduces user-observable changes.
  • The build (bundle exec rake) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit).

If you have created a new cop:

  • Added the new cop to config/default.yml.
  • The cop is configured as Enabled: pending in config/default.yml.
  • The cop is configured as Enabled: true in .rubocop.yml.
  • The cop documents examples of good and bad code.
  • The tests assert both that bad code is reported and that good code is not reported.
  • Set VersionAdded: "<<next>>" in default/config.yml.

If you have modified an existing cop's configuration options:

  • Set VersionChanged: "<<next>>" in config/default.yml.

This cop detects redundant `pending` or `skip` calls inside examples
that are already marked as skipped. When an example is skipped using
`xit`, `xspecify`, `xexample`, or skip/pending metadata, adding a
`pending` or `skip` call inside the example body is redundant and
creates unnecessary duplication.

It's common to see code like this:

```ruby
xspecify do
  pending 'Need to upgrade to the latest HTTP gem version'
  expect(pinger.call).to eq(204)
end

xit 'answers success status' do
  pending 'Need to upgrade HTTP gem before this will work again'
  expect(pinger.call).to eq(200)
end
```

In these cases:
- The example is already skipped via `xspecify`/`xit`
- The `pending` call inside is redundant and never executed
- This creates confusion about where the skip/pending is actually applied
- The reason message in `pending` is not displayed (the example is
  already skipped at method level)

This cop flags such redundant calls and suggests either:
1. Remove the redundant `pending`/`skip` call entirely, OR
2. Use a regular example method (`it`, `specify`) if the pending call
   is actually needed

The cop detects redundancy in the following cases:

1. Skipped example methods:
   - `xit`, `xspecify`, `xexample`

2. Skip/pending metadata:
   - Symbol metadata: `it 'test', :skip do`
   - Symbol metadata: `it 'test', :pending do`
   - Hash metadata: `it 'test', skip: true do`
   - Hash metadata: `it 'test', skip: 'reason' do`
   - Hash metadata: `it 'test', pending: 'reason' do`

3. Both `pending` and `skip` calls:
   - Detects both `pending 'reason'` and `skip 'reason'`

The cop does NOT flag these legitimate uses:

1. Regular examples with pending/skip:
   ```ruby
   it 'does something' do
     pending 'not yet implemented'
     expect(something).to be_truthy
   end
   ```

2. Conditional skip/pending:
   ```ruby
   xit 'does something' do
     setup_something
     skip 'setup failed' if setup_failed?  # Not first statement
     expect(something).to be_truthy
   end
   ```

3. Metadata with `false` value:
   ```ruby
   it 'does something', skip: false do
     pending 'conditional pending'
     expect(something).to be_truthy
   end
   ```

- Checks only the first statement in the example body
- Supports both regular blocks (`do...end`) and numblocks
- Handles examples with single or multiple statements
- Handles one-liner syntax: `xit('test') { pending 'reason' }`

- Reduces confusion about where examples are actually skipped
- Eliminates redundant code
- Makes skip/pending reasons visible (use metadata instead of body)
- Improves test suite maintainability
@ydah ydah requested a review from a team as a code owner October 5, 2025 11:57
Copy link
Member
@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't inline comment 🤦

I wonder, how many callbacks we'll get for on_block, and for each we would have to check those two node matchers, skipped_example? and skipped_by_metadata?. Won't this impact the performance?
Wouldn't checking send nil? { :skip :pending }, finding its nearest ancestor (any)block element and making those checks against it reduce the work to be done?

I wonder, can this happen in the wild? Have you by a change ran the cop against some real-world repos?


RSpec/IncludeExamples: {Enabled: true}
RSpec/LeakyLocalVariable: {Enabled: true}
RSpec/RedundantPending: {Enabled: false}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RSpec/RedundantPending: {Enabled: false}
RSpec/RedundantPending: {Enabled: true}

PATTERN

# @!method skipped_by_metadata?(node)
def_node_matcher :skipped_by_metadata?, <<~PATTERN
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reuse skipped_in_metadata? from the SkipOrPending mixin?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0