Building the User Interface

We can add a debugging hook to show us what’s going on in our failing scenario. Create a file features/support/debugging.rb:

 After ​do​ |scenario|
  save_and_open_page ​if​ scenario.failed?
 end

The method save_and_open_page is provided by Capybara, but it relies on the launchy gem that isn’t installed automatically. We’ll add it to our Gemfile:

 source "https://rubygems.org"
 
 gem 'sinatra', '2.0.0.beta.2'
 
 group :development do
  gem 'rspec', '3.5.0'
  gem 'cucumber', '3.0.0.pre.1'
  gem 'capybara', '2.9.1'
  gem 'launchy', '2.4.3'
 end

Now run bundle to install the new gem.

What you should see if you run cucumber now is the web page that Cucumber can see has been saved to disk and opened up in your browser. This can be a very helpful debugging tool. What it’s shown us is the simple greeting message we added earlier when we first added Sinatra. We need to change the Sinatra app to return a simple HTML form.

 require ​'sinatra'
 
 get ​'/'​ ​do
 %{
  <html>
  <body>
  <form action="/withdraw" method="post">
  <label for="amount">Amount</label>
  <input type="text" id="amount" name="amount">
  <button type="submit">Withdraw</button>
  </form>
  </body>
  </html>
  }
 end
 
 post ​'/withdraw'​ ​do
  fail ​"I don't know how to withdraw yet, sorry"
 end

We’ve hard-coded the HTML for the form right in the Sinatra web request handler so that you can easily see what’s going on. The form contains a single field with a label and a submit button that posts back to the server. We’ve also added a new post request handler so that the form has somewhere to post back to, although it’s just a stub that will raise an error if it’s called.

Try running cucumber now. If everything has gone according to plan, you’ll see the error I don’t know how to withdraw yet, sorry displayed in Cucumber’s output, and save_and_open_page should be showing the form, since that’s the last thing the browser was looking at. Our next step is to implement the right code in the post request handler to actually withdraw the cash.

Dispensing the Cash

We’ve already built a domain model, so we have a significant head start. The class that knows how to carry out the withdrawal is Teller, so we’ll need to create an instance of that class when we receive the posted form data on the web server. To create an instance of the Teller, we need a CashSlot to pass to the constructor. The slightly tricky thing about this is that we need to make sure that the step definitions are also looking at the same instance of CashSlot; otherwise, they won’t be able to see the cash we’ve dispensed.

We can use Sinatra’s settings to store a single instance of CashSlot where it can be accessed by both the web application and the tests. First we’ll create the setting and use it in the web application:

 set ​:cash_slot​, CashSlot.new
 post ​'/withdraw'​ ​do
  teller = Teller.new(settings.cash_slot)
  fail ​"I don't know how to withdraw yet, sorry"
 end

Ah, now if we want to call withdraw_from on the teller, we’ll also need an Account. As our project progresses, we’ll probably end up determining the Account by reading the user’s card, but at the moment our domain model doesn’t go that far. For the time being, what we need is a way for the step definitions to be able to say to the web application look, just trust me and use this account.

Let’s create another setting on our web application so it can share the account with the tests. This time we won’t give it a default value, however, because the ATM shouldn’t have a default account—it should be determined from the user’s interactions with the ATM when they push their card into the slot and enter their PIN. In case we forget to do this in future scenarios, we’ll cause it to throw an error if it hasn’t been set:

 set ​:cash_slot​, CashSlot.new
 set ​:account​ ​do
  fail ​'account has not been set'
 end
 post ​'/withdraw'​ ​do
  teller = Teller.new(settings.cash_slot)
  teller.withdraw_from(settings.account, params[​:amount​].to_i)
 end

Now we can try to call Teller#withdraw_from. Update your code to look like the previous code, and run cucumber. You should see the error message from the account setting:

 Feature: Cash Withdrawal
 
  Scenario: Successful withdrawal from an account in credit
  Given my account has been credited with $100
  When I withdraw $20
  account has not been set (RuntimeError)
  ./lib/nice_bank.rb:57
  ./lib/nice_bank.rb:61
  ./features/support/world_extensions.rb:10
  ./features/step_definitions/teller_steps.rb:2
  features/cash_withdrawal.feature:4
  Then $20 should be dispensed
  And the balance of my account should be $80
 Failing Scenarios:
 cucumber features/cash_withdrawal.feature:2
 
 1 scenario (1 failed)
 4 steps (1 failed, 2 skipped, 1 passed)
 0m0.061s

We need to update our support code so it makes use of these two touchpoints for sharing the CashSlot and Account. For the CashSlot, we’ll change our support module to read the web application’s setting instead of creating its own instance:

 def​ cash_slot
  Sinatra::Application.cash_slot
 end
 
 def​ teller
  @teller ||= UserInterface.new
 end
 end
 World(KnowsTheUserInterface)

Then set the Account on the web application as we fill out the withdrawal form:

 class​ UserInterface
 include​ Capybara::DSL
 
 def​ withdraw_from(account, amount)
» Sinatra::Application.account = account
  visit ​'/'
  fill_in ​'Amount'​, ​:with​ => amount
  click_button ​'Withdraw'
 end
 end

This is a temporary shortcut around the process of actually authenticating the user with their physical card and PIN, because we don’t want to worry about that part of the process yet.

Run cucumber now, and the scenario should be passing:

 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)
 0m0.044s

Phew! We’ve managed to convert our tests to run against a web interface, and all we needed to do was change the way our World module’s methods were implemented. If we wanted, we could set up our tests to switch between the two modules, giving us fast tests that go directly to the domain model and slower, more thorough tests that go right through the user interface.

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

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