Using RSPec's before and after blocks

I believe that let, let! and before should be used for test setup, and after should be used for test teardown. I also follow the Arrange, Act, Assert test structure to organise my tests and make them easier to reason about.

Yesterday, during a code review, I tried to explain that to one of my co-workers. He wanted me to change this:

# made up code that I have
describe GameRetriever do
  subject(:game_retriever) { described_class.new }

  describe '#retrieve_games' do
    it 'retrieves games from an HTTP data source', vcr: true do
      games = game_retriever.retrieve_games
      games = JSON.parse(games)

      expect(games.first.keys).to eq(%w(id status players))
    end
  end
end

into this:

# his suggestion
describe GameRetriever do
  subject(:game_retriever) { described_class.new }
  let(:games) { game_retriever.retrieve_games }
  let(:parsed_games) { JSON.parse(games) }

  describe '#retrieve_games' do
    it 'retrieves games from an HTTP data source', vcr: true do
      expect(parsed_games.first.keys).to eq(%w(id status players))
    end
  end
end

He also suggested me to use after to remove duplication in tests. I know that duplication is wrong, but I believe that it is not harmful in this scenario and it having the code explicitly written can make the code easier to understand:

# made up code that I have
describe StartGame do
  let(:action) { described_class.new }
  
  describe '#call' do
    context 'when ...' do
      expect(player).to be_playing
      action.call
    end

    context 'when ...' do
      expect(player).to be_idle
      action.call
    end

    context 'when ...' do
      expect(player).not_to be_playing
      action.call
    end
  end
end

into this:

# what he suggested
describe StartGame do
  let(:action) { described_class.new }

  after { action.call }
  
  describe '#call' do
    context 'when ...' do
      expect(player).to be_playing
    end

    context 'when ...' do
      expect(player).to be_idle
    end

    context 'when ...' do
      expect(player).not_to be_playing
    end
  end
end

Am I wrong in believing that this is the best way to structure our test suite or should I come up with better arguments?

It seems everyone has their own particular quirks when writing RSpec.

I prefer to minimise my use of let, but some people rely on it heavily. I tolerate some duplication in tests for the sake of clarity, but others will take DRY to extremes.

Part of the difficulty is that RSpec itself is fairly un-opinionated – it doesn’t seem to mind how you choose to write your specs.