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 | |
---|---|
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 | |
---|---|
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. |