Our goal is to introduce a user interface for requesting the cash withdrawal. We want Cucumber to cover us as we make these changes, so we need to change how our test code interacts with the application. Up until now, all our step definitions were talking directly to the domain model. We’re going to change that so that some of them hit the new user interface instead (see Figure 8, Introducing the user interface). Which steps need to change?
Let’s take a look at our scenario again:
support_code/11/features/cash_withdrawal.feature | |
| 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 |
We haven’t specified anything about how we do the cash withdrawal, so there’s nothing we need to change about the scenario at all. Great! Let’s jump down into the step definition for withdrawing cash and see what needs to change there:
support_code/11/features/step_definitions/teller_steps.rb | |
| When /^I withdraw (#{CAPTURE_CASH_AMOUNT})$/ do |amount| |
| teller.withdraw_from(my_account, amount) |
| end |
All we’re doing here is calling withdraw_from on something. Right now that something is an instance of the Teller class in our domain model. But what if we made teller return something else, like an object that represents the user interface? That’s the beauty of object-oriented programming—as long as the object understands the withdraw_from method, this step definition is going to be happy. Let’s leave this step definition alone and drop down into our World module:
support_code/11/features/support/world_extensions.rb | |
| module KnowsTheDomain |
| def my_account |
| @my_account ||= Account.new |
| end |
| |
| def cash_slot |
| @cash_slot ||= CashSlot.new |
| end |
| |
| def teller |
| @teller ||= Teller.new(cash_slot) |
| end |
| end |
| |
| World(KnowsTheDomain) |
What we need to do is reimplement the teller method to return a class that knows how to automate the user interface. For now, just build an empty class with the right method on it, and change teller to return an instance of the new class:
support_code/12/features/support/world_extensions.rb | |
| module KnowsTheUserInterface |
| class UserInterface |
| def withdraw_from(account, amount) |
| end |
| end |
| |
| def my_account |
| @my_account ||= Account.new |
| end |
| |
| def cash_slot |
| @cash_slot ||= CashSlot.new |
| end |
| |
| def teller |
| @teller ||= UserInterface.new |
| end |
| end |
| World(KnowsTheUserInterface) |
Now we’ve disconnected the action-invoking When step in our scenario from the domain model and connected it to this new UserInterface class. We’ve also renamed the module to reflect its new responsibility. If you run cucumber now, you should see the scenario fail:
| 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 |
| I’m empty! (RuntimeError) |
| ./lib/nice_bank.rb:30 |
| ./features/step_definitions/cash_slot_steps.rb:2 |
| features/cash_withdrawal.feature:5 |
| 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, 1 skipped, 2 passed) |
| 0m0.002s |
The scenario has failed because our new support module hasn’t been wired up to the system yet, so nothing has been found in the cash slot. Now we have a goal: get that scenario to pass again, but this time, through the user interface.
So, what should this shiny new user interface look like? We gathered around the whiteboard with our user experience team and sketched out a wireframe for the first iteration of the cash withdrawal form. The plan is for it to look roughly like Figure 9, Wireframe for cash withdrawal user interface.
Let’s flesh out our UserInterface class to talk to that form:
support_code/13/features/support/world_extensions.rb | |
| class UserInterface |
| include Capybara::DSL |
| |
| def withdraw_from(account, amount) |
| visit '/' |
| fill_in 'Amount', :with => amount |
| click_button 'Withdraw' |
| end |
| end |
We’ve included Capybara’s DSL module in the UserInterface class to give us these new methods that let us carry out actions on the web user interface. First we visit the home page, then we fill_in the field labeled Amount, and finally we click the Withdraw button. This is the design we just looked at on the wireframe, formalized in code.
When we run cucumber against this, it’s going to fail of course, because we haven’t built the form yet. Let’s run it anyway just to check we’re on track:
| Feature: Cash Withdrawal |
| |
| Scenario: Successful withdrawal from an account in credit |
| Given my account has been credited with $100 |
| When I withdraw $20 |
| Unable to find field "Amount" (Capybara::ElementNotFound) |
| ./features/support/world_extensions.rb:9 |
| ./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.148s |
OK, so Capybara is telling us that there isn’t an Amount field to fill in. Wouldn’t it be nice to be able to see what Cucumber is seeing? We could start Sinatra and run a manual test, but it would be more useful to see exactly what’s in the browser when the test fails. To do this, we need to take some time out to learn about a new feature of Cucumber.
18.225.149.238