RSpec, use_transaction_fixtures and after_commit
Tags: ruby, rails, rspec, testing
Published: 2021-08-18 03:43:38

Hello everyone, this is my first post on Dev.to, I want to use this post to share some knowledge and practice my skills as writer. Thank you for reading my post.

### Firstly, some information about our project

We use a legacy project with Rails 4, RSpec (also FactoryGirl) with use_transaction_fixtures activated [_(see more details)_](https://relishapp.com/rspec/rspec-rails/v/5-0/docs/transactions). This means that every example runs within a transaction, and then it removes that data by simply rolling back the transaction at the end of the example.

When we need to test `after_commit` callback we execute the method `object.run_callbacks(:commit)` after the operation (create, update or destroy).

### What did I want to do?

I was writing a test for my model and there were three callbacks of type after_commit, one for each transaction type (create, update and destroy). I wanted to ensure that the system would generate the information correctly.

Now, imagine you create one record, then you update the same record and after you delete the record.

Below is an example of the code.

```ruby
it 'notify service at every change' do
  total_jobs = NotifyWorker.jobs.count
  expect(total_jobs).to be_zero
​
  person = create(:person)
  person.run_callbacks(:commit)
​
  expect(NotifyWorker.jobs.count).to eq(total_jobs + 1)
  expect(NotifyWorker.jobs[0]['args']).to eq(
    [
      'create',
      {
        'id' => person.id,
        'name' => person.name
      }
    ]
  )
​
  person.update(name: 'test')
  person.run_callbacks(:commit)
​
  expect(NotifyWorker.jobs.count).to eq(total_jobs + 2)
  expect(NotifyWorker.jobs[1]['args']).to eq(
    [
      'update',
      {
        'id' => person.id,
        'name' => person.name
      }
    ]
  )
​
  person.destroy
  person.run_callbacks(:commit)
​
  expect(NotifyWorker.jobs.count).to eq(total_jobs + 3)
  expect(NotifyWorker.jobs[2]['args']).to eq(
    [
      'destroy',
      {
        'id' => person.id
      }
    ]
  )
end
```

I expected the app to create three jobs in sequence, one for each transaction type, but that's not what happened. 

In this case of the update it genetared a job of type "create" and when I deleted the record, it generated a job correctly, so the problem was in the update, but I didn't understand why.

I searched on google some information that might help and found a question on StackOverflow about the same problem I had.

After create the record (`person = create(:person)`), a instance of `@_start_transaction_state` is initialized, but never cleared. [See more details](https://stackoverflow.com/questions/33940268/after-commit-callback-on-update-doesnt-trigger)
This variable is used to control which is the action in transaction.

So, I just had to clear the variable, for that you can use the `clear_transaction_record_state` method before executing `after_commit`, but this is a protected method, so you should use the `send` method, like that.

`object.send(:clear_transaction_record_state)`

Below you can see the final result.

```ruby
it 'notify service at every change' do
  total_jobs = NotifyWorker.jobs.count
  expect(total_jobs).to be_zero
​
  person = create(:person)
  person.run_callbacks(:commit)
​
  expect(NotifyWorker.jobs.count).to eq(total_jobs + 1)
  expect(NotifyWorker.jobs[0]['args']).to eq(
    [
      'create',
      {
        'id' => person.id,
        'name' => person.name
      }
    ]
  )
​
  person.send(:clear_transaction_record_state)
  person.update(name: 'test')
  person.run_callbacks(:commit)
​
  expect(NotifyWorker.jobs.count).to eq(total_jobs + 2)
  expect(NotifyWorker.jobs[1]['args']).to eq(
    [
      'update',
      {
        'id' => person.id,
        'name' => person.name
      }
    ]
  )
​
  person.send(:clear_transaction_record_state)
  person.destroy
  person.run_callbacks(:commit)
​
  expect(NotifyWorker.jobs.count).to eq(total_jobs + 3)
  expect(NotifyWorker.jobs[2]['args']).to eq(
    [
      'destroy',
      {
        'id' => person.id
      }
    ]
  )
end
```

Again thank you for reading my post and I see you later.

References:
- https://relishapp.com/rspec/rspec-rails/v/5-0/docs/transactions
- https://apidock.com/rails/ActiveRecord/Transactions/clear_transaction_record_state
- https://stackoverflow.com/questions/33940268/after-commit-callback-on-update-doesnt-trigger
- https://apidock.com/rails/v4.0.2/ActiveRecord/Transactions/clear_transaction_record_state
- https://github.com/rails/rails/blob/4-0-stable/activerecord/lib/active_record/transactions.rb#L384
- https://github.com/rails/rails/blob/main/activerecord/lib/active_record/transactions.rb