Let’s turn our attention to the final piece of the puzzle: querying expenses back from the database. First, you’ll need a failing spec that records a few expenses into the ledger, with some of the expenses on the same date. Querying for that date should return only the matching expenses.
Add the following specs inside the RSpec.describe block in spec/integration/app/ ledger_spec.rb:
| describe '#expenses_on' do |
| it 'returns all expenses for the provided date' do |
| result_1 = ledger.record(expense.merge('date' => '2017-06-10')) |
| result_2 = ledger.record(expense.merge('date' => '2017-06-10')) |
| result_3 = ledger.record(expense.merge('date' => '2017-06-11')) |
| |
| expect(ledger.expenses_on('2017-06-10')).to contain_exactly( |
| a_hash_including(id: result_1.expense_id), |
| a_hash_including(id: result_2.expense_id) |
| ) |
| end |
| it 'returns a blank array when there are no matching expenses' do |
| expect(ledger.expenses_on('2017-06-10')).to eq([]) |
| end |
| end |
The overall flow—adding three expenses and then searching for two of them by date—looks similar to what we did in the acceptance spec in Querying the Data. Here, you’re testing at the level of the individual Ledger object, rather than the entire app.
We’re using another composable matcher here to combine two matchers you’ve used before—contain_exactly and a_hash_including—into a new matcher that describes a nested data structure.
Run the spec file to make sure both specs fail:
| $ bundle exec rspec spec/integration/app/ledger_spec.rb |
| |
| Randomized with seed 3824 |
| |
| ExpenseTracker::Ledger |
| #record |
| with a valid expense |
| successfully saves the expense in the DB |
| when the expense lacks a payee |
| rejects the expense as invalid |
| #expenses_on |
| returns a blank array when there are no matching expenses (FAILED - 1) |
| returns all expenses for the provided date (FAILED - 2) |
| |
| Failures: |
| |
| 1) ExpenseTracker::Ledger#expenses_on returns a blank array when there ↩ |
| are no matching expenses |
| Failure/Error: expect(ledger.expenses_on(’2017-06-10’)).to eq([]) |
| |
| expected: [] |
| got: nil |
| |
| (compared using ==) |
| # ./spec/integration/app/ledger_spec.rb:59:in ‘block (3 levels) in ↩ |
| <module:ExpenseTracker>’ |
| # ./spec/support/db.rb:9:in ‘block (3 levels) in <top (required)>’ |
| # ./spec/support/db.rb:9:in ‘block (2 levels) in <top (required)>’ |
| |
| 2) ExpenseTracker::Ledger#expenses_on returns all expenses for the ↩ |
| provided date |
| Failure/Error: |
| expect(ledger.expenses_on(’2017-06-10’)).to contain_exactly( |
| a_hash_including(id: result_1.expense_id), |
| a_hash_including(id: result_2.expense_id) |
| ) |
| |
| expected a collection that can be converted to an array with ↩ |
| ‘#to_ary‘ or ‘#to_a‘, but got nil |
| # ./spec/integration/app/ledger_spec.rb:52:in ‘block (3 levels) in ↩ |
| <module:ExpenseTracker>’ |
| # ./spec/support/db.rb:9:in ‘block (3 levels) in <top (required)>’ |
| # ./spec/support/db.rb:9:in ‘block (2 levels) in <top (required)>’ |
| |
| Finished in 0.02766 seconds (files took 0.16616 seconds to load) |
| 4 examples, 2 failures |
| |
| Failed examples: |
| |
| rspec ./spec/integration/app/ledger_spec.rb:58 # ↩ |
| ExpenseTracker::Ledger#expenses_on returns a blank array when there are no ↩ |
| matching expenses |
| rspec ./spec/integration/app/ledger_spec.rb:47 # ↩ |
| ExpenseTracker::Ledger#expenses_on returns all expenses for the provided date |
| |
| Randomized with seed 3824 |
Now, you’re ready to implement the logic to make the spec pass. The Ledger class will need to query the database’s expenses table for rows that have a matching date. You can use Sequel’s where method to filter by the date field.
In the exercises at the end of the previous chapter, you defined an empty expenses_on method for the Ledger class. Fill in its body with the following code:
| def expenses_on(date) |
| DB[:expenses].where(date: date).all |
| end |
Your integration specs should pass now:
| $ bundle exec rspec spec/integration/app/ledger_spec.rb |
| |
| Randomized with seed 22267 |
| |
| ExpenseTracker::Ledger |
| #record |
| with a valid expense |
| successfully saves the expense in the DB |
| when the expense lacks a payee |
| rejects the expense as invalid |
| #expenses_on |
| returns all expenses for the provided date |
| returns a blank array when there are no matching expenses |
| |
| Finished in 0.01832 seconds (files took 0.16797 seconds to load) |
| 4 examples, 0 failures |
| |
| Randomized with seed 22267 |
At this point, all the logic and specs are implemented at every layer of the app. It’s time to see if our outermost acceptance spec passes yet. Let’s run the entire suite:
| $ bundle exec rspec |
| |
| Randomized with seed 21580 |
| F............ |
| |
| Failures: |
| |
| 1) Expense Tracker API records submitted expenses FIXED |
| Expected pending ’Need to persist expenses’ to fail. No error was ↩ |
| raised. |
| # ./spec/acceptance/expense_tracker_api_spec.rb:22 |
| |
| Finished in 0.04844 seconds (files took 0.22185 seconds to load) |
| 13 examples, 1 failure |
| |
| Failed examples: |
| |
| rspec ./spec/acceptance/expense_tracker_api_spec.rb:22 # Expense Tracker ↩ |
| API records submitted expenses |
| |
| Randomized with seed 21580 |
RSpec is listing one failure, but says the spec has been fixed. Back in Saving Your Progress: Pending Specs, you marked the acceptance spec as pending because it wasn’t yet passing. That means it’ll pass as soon as you remove the pending line.
Search inside spec/acceptance/expense_tracker_api_spec.rb for the line containing the word pending, and delete the whole line. Once that’s done, your acceptance specs will pass, too:
| $ bundle exec rspec |
| |
| Randomized with seed 14629 |
| ............. |
| |
| Finished in 0.04986 seconds (files took 0.21623 seconds to load) |
| 13 examples, 0 failures |
| |
| Randomized with seed 14629 |
Nice! Over the span of just a few chapters, you’ve implemented a working JSON API to track expenses. At every step of the process, you’ve used RSpec to guide your design, catch regressions, and build your app with confidence.