Reading and Writing to the Database

When we modified the Account earlier, we made it write out each transaction message as amount,account_number. The TransactionProcessor needs the account_number to find an account in the database and update its balance:

 require_relative ​'transaction_queue'
 require_relative ​'account'
 
 transaction_queue = TransactionQueue.new
 
 puts ​"transaction processor ready"
 loop​ ​do
  transaction_queue.read ​do​ |message|
  sleep 1
» transaction_amount, number = message.split(​/,/​)
» account = Account.find_by_number!(number.strip)
» new_balance = account.balance + transaction_amount.to_i
» account.balance = new_balance
» account.save
 end
 end

However, looking at the message files in the messages directory, we see that the account numbers are missing. That’s because we haven’t assigned an account number anywhere! Let’s go back to the code where we instantiate an Account and make it assign a number, and while we’re at it, let’s save it to the database using ActiveRecord’s create! method:

 def​ my_account
  @my_account ||= Account.create!(​:number​ => ​"test"​, ​:balance​ => 0)
 end

The step that failed is now passing, and we’re left with a last failing step:

 == 1 CreateAccounts: migrating ================================================
 -- create_table(:accounts)
  -> 0.0010s
 == 1 CreateAccounts: migrated (0.0010s) =======================================
 
 Starting transaction_processor in ~/message_queues/01 with
  'ruby lib/transaction_processor.rb'
 transaction processor ready
 Server transaction_processor (94557) is up.
 Feature: Cash Withdrawal
 
  Scenario: Successful withdrawal from an account in credit
  Given my account has been credited with $100
  When I withdraw $20
  Then $20 should be dispensed
 lib/transaction_processor.rb:12:in `block (2 levels) in <main>':
  undefined method `strip' for nil:NilClass (NoMethodError)
  from ~/databases/03/lib/transaction_queue.rb:21:in `read'
  from lib/transaction_processor.rb:8:in `block in <main>'
  from lib/transaction_processor.rb:7:in `loop'
  from lib/transaction_processor.rb:7:in `<main>'
  And the balance of my account should be $80
 
  expected: 80
  got: 0
 
  (compared using ==)
  (RSpec::Expectations::ExpectationNotMetError)
  ./features/step_definitions/account_steps.rb:7
  ./features/support/async_support.rb:8
  ./features/support/async_support.rb:6
  ./features/support/async_support.rb:6
  ./features/step_definitions/account_steps.rb:7
  features/cash_withdrawal.feature:6
 
 Failing Scenarios:
 cucumber features/cash_withdrawal.feature:2
 
 1 scenario (1 failed)
 4 steps (1 failed, 3 passed)
 0m2.165s
 Shutting down transaction_processor (94557)
 Server transaction_processor (94557) is shut down

We expected the balance to be $80, but it looks like it’s still $0. Where did the money go? Let’s look in the database.

 $ ​​sqlite3​​ ​​db/bank.db
 sqlite>​​ ​​select​​ ​​*​​ ​​from​​ ​​accounts;
 1|test|80
 sqlite>​​ ​​.quit

Phew! The money is safe in our account. It turns out the step failed because we are still looking at the original instance of the account record—stored in @my_account—from when we created it with a zero balance. Even though the underlying database row has been modified by the TransactionProcessor, ActiveRecord doesn’t know that the record we have is out-of-date, so we need to tell it to reload the record from the database:

 Then(​/^the balance of my account should be (​​#{​CAPTURE_CASH_AMOUNT​}​​)$/​) ​do​ |amount|
  eventually { expect(my_account.reload.balance).to eq(amount) }
 end

Let’s run the scenario again:

 == 1 CreateAccounts: migrating ================================================
 -- create_table(:accounts)
  -> 0.0012s
 == 1 CreateAccounts: migrated (0.0013s) =======================================
 
 Starting transaction_processor in ~/message_queues/01 with
  'ruby lib/transaction_processor.rb'
 transaction processor ready
 Server transaction_processor (94557) is up.
 Feature: Cash Withdrawal
 
  Scenario: Successful withdrawal from an account in credit
  Given my account has been credited with $100
  When I withdraw $20
  Then $20 should be dispensed
  And the balance of my account should be $80
 
 1 scenario (1 passed)
 4 steps (4 passed)
 0m2.171s
 Shutting down transaction_processor (94557)
 Server transaction_processor (94557) is shut down

This time the first step in our scenario, which ran fine the last time we run the scenario, has failed. We have stumbled upon one of the most common problems of automated tests for a system using a database. The previous test run left data in the database, and running it again makes it fail. We can’t create an account because the Account number has to be unique, and a row for that Account was left behind by the previous test run. We have a leaky scenario!

The good thing about common problems is that there often is a common solution. We have to make sure each scenario starts with a clean database. There are two strategies to achieve this—transaction and truncation. We’ll explore both, starting with transactions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset