Handling Success

To pass this spec, the /expenses route of our API needs to do three things:

  • Parse an expense from the request body

  • Use its Ledger (either a real database-based one or a fake one for testing) to record the expense

  • Return a JSON document containing the resulting expense ID

Change your /expenses route inside app/api.rb to the following code:

 post ​'/expenses'​ ​do
  expense = JSON.parse(request.body.read)
  result = @ledger.record(expense)
  JSON.generate(​'expense_id'​ => result.expense_id)
 end

Now, rerun the specs:

 $ ​​bundle exec rspec spec/unit/app/api_spec.rb
  truncated
 
 Finished in 0.02645 seconds (files took 0.14491 seconds to load)
 4 examples, 0 failures, 3 pending
 
 Randomized with seed 23924

The API is now correctly recording the expense and returning the result we expect.

Now that we know the app is returning the expense ID correctly, let’s move on to the next bit of behavior: rendering the correct HTTP status code. Fill in the body of the ’responds with a 200 (OK)’ spec as follows:

 it​ ​'responds with a 200 (OK)'​ ​do
  expense = { ​'some'​ => ​'data'​ }
 
 allow​(ledger).to receive(​:record​)
  .with(expense)
  .and_return(RecordResult.new(​true​, 417, ​nil​))
 
  post ​'/expenses'​, JSON.generate(expense)
 expect​(last_response.status).to eq(200)
 end

Go ahead and run this spec file:

 $ ​​bundle exec rspec spec/unit/app/api_spec.rb
 
 Randomized with seed 55289
 
 ExpenseTracker::API
  POST /expenses
  when the expense is successfully recorded
  returns the expense id
  responds with a 200 (OK)
  when the expense fails validation
  responds with a 422 (Unprocessable entity) (PENDING: Not yet
  implemented)
  returns an error message (PENDING: Not yet implemented)
 
  truncated
 
 Finished in 0.02565 seconds (files took 0.12232 seconds to load)
 4 examples, 0 failures, 2 pending
 
 Randomized with seed 55289

It passes, because Sinatra returns a 200 HTTP status code unless an error occurs or you explicitly set one. That should make you wonder whether or not the test actually works. Let’s temporarily break the app code to make sure the test will catch it:

Always See Your Specs Fail

images/aside-icons/info.png

Tests, like implementation code, can contain bugs, but we don’t have tests for our tests! So, check each test by making it go red, confirming it fails the way you expect, and making it pass.

 post ​'/expenses'​ ​do
» status 404
 
  expense = JSON.parse(request.body.read)
  result = @ledger.record(expense)
  JSON.generate(​'expense_id'​ => result.expense_id)
 end

Now, rerunning the spec fails as expected:

 $ ​​bundle exec rspec spec/unit/app/api_spec.rb
  truncated
 
 Failures:
 
  1) ExpenseTracker::API POST /expenses when the expense is successfully
  recorded responds with a 200 (OK)
  Failure/Error: expect(last_response.status).to eq(200)
 
  expected: 200
  got: 404
 
  (compared using ==)
  # ./spec/unit/app/api_spec.rb:41:in ‘block (4 levels) in
  <module:ExpenseTracker>’
 
 Finished in 0.03479 seconds (files took 0.14115 seconds to load)
 4 examples, 1 failure, 2 pending
 
 Failed examples:
 
 rspec ./spec/unit/app/api_spec.rb:33 # ExpenseTracker::API POST /expenses
 when the expense is successfully recorded responds with a 200 (OK)
 
 Randomized with seed 32399

Make sure you un-break the app before continuing. Once you do that, you’ll be back to two passing specs. Now, we can turn our attention to test maintainability. There’s a lot of duplicated code in these two test cases. Before moving on to the next section, consider how you might make the code a little less repetitive.

Refactor While Green

images/aside-icons/info.png

It’s tempting to start factoring out duplicate code while you’re still writing your specs. Avoid that temptation: get your specs passing first, and then refactor. That way, you can use your specs to check your refactoring.

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

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