This is Part 3 in a multi-part series to detail the creation of a “simple” project combining Ruby, MongoDB, RSpec, Sinatra, and Capybara in preperation for a larger-scale side project set to begin January 2013. For more in this series, see the Pokephile category. Part 3 of this series describes using Capybara to test a Sinatra application. The code for this side-project is located on Github, and the final product can be found here.
Now that the database is populated with data and I’ve switched over to a simpler interface with Mongo, I can actually start creating a UI. For simplicity’s sake, I like to use Sinatra on small projects. Sinatra makes it easy to create a web application with minimal effort. With an emphasis on testing this series, I want to be sure to throughly test my UI and any application integration. Cucumber is a brilliant DSL which allows a programmer to describe in plain English how an application should be behaving. The Capybara gem is a Rack app that simulates running your application and performing basic user tasks, such as clicking a button, following a link, or, on a lower level, looking at your HTML source. Install Capybara like so:
1
|
|
Here’s what I want my application to do:
- User is on home page.
- User enters name of Pokemon and presses ‘Search’.
- User is redirected to search page.
- User can see some information about Pokemon.
- User can repeat the search process.
Error scenario:
- User enters garbage data.
- User is redirected to search page.
- User sees error message.
- User can repeat search process.
So I’ll begin by writing my features. Cucumber syntax is meant to be readable by non-technical persons, so the “code” may look a bit odd. All Cucumber feature files are written something like this:
1 2 3 4 5 6 7 8 |
|
The “Feature” lines are not used in testing; they are simply to give some relevance to the file’s feature and to attempt to prevent scope creep in the file. The “Scenario” lines are what’s important. The first line is the test name, the “Given” line is the pre-condition, and the final line is what should be observed.
In reality, I have only one main feature: “Search.” One can argue that I also have a feature of “seeing” my home page and my search page, but those are both trivial cases, so for the purpose of this blog post, I’ll skip such tests. Both my success and error case revolves around searching, and there’s not really much else to do in the app. I have to create a directory for the cukes, and that directory is called “features.” I create such a directory in my “project/tools/test” directory and create a new file called “search.feature.” Now I’ll write the feature:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Now I’ve overlooked a lot of tests that one would normally write while doing this, such as verifying that the search bar and search buttons exist and are enabled, but I’d like to keep it simple for now and just stick to testing my search feature. What do these tests do? The first scenario starts on the home page, enters data in the search box, presses the search button, and then verifies that all expected Pokemon data is visible. When writing cukes, I can append statements with an “And” statement as seen above. Run Cucumber:
1
|
|
Cucumber doesn’t exactly give us errors, but it also doesn’t give us success. Fortunately, what it did give us was sample code for all of the steps we need to write. So, let’s perform some copy/paste magic and create a steps file:
1 2 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
If I futilely run “cucumber” again now, my tests still don’t pass because I haven’t actually implemented my steps. This is where Capybara comes in. I found that a Capybara cheat sheet is quite helpful while writing out my steps. The syntax I’m going to use is similar to RSpec, except that it includes some Capybara methods. The first two test steps I want to deal with are the “Given” steps.
1 2 3 4 5 6 7 8 |
|
All these statements are just Regular Expressions, as indicated by the /^$/. The regex acts as a sort of method name that Cucumber finds to run the steps. Given I am on the home page is trivial: just ‘visit’ the index. Given I am on the search page will first require me to click the “search” button. This is valid because my spec above says this is how to get to the search page. Now can do the ‘When’ statements.
1 2 3 4 5 6 7 |
|
When I click just needs to click a button/link/whatever on the screen. The “(.*?)” is a regular expression that will match anything in quotes and assign it to the variable ‘arg1.’ So I can give any button description and Capybara will try to click a button with the given content. When I type in the search bar takes the regex arg1 and uses the “fill_in” method to fill in a text input with id “pokemon-input.” The rest of the steps are all about what should be observed after performing the Given/When steps.
1 2 3 4 5 6 7 8 9 10 11 |
|
Now I have all of the necessary steps defined! So, I’ll run Cucumber and… Actual errors! None of my four tests made it past the Given step, so I see the output ‘(4 failed, 19 skipped).’ The only way to fix these errors is to finally start writing a web application. So I’ll move back out to the root of my project directory and create a file for my application called ‘app.rb’ and give it the most basic information to run. And, if you haven’t already, install Sinatra.
1
|
|
1 2 3 4 5 6 7 8 |
|
The Application class inherits from the Sinatra::Base class. This allows me to define a ‘get’ operation to perform actions and load a web page. ‘get \’\‘ do’ signifies the first page a user sees when they go to my web application, commonly known as a home or index page. I plan to use HAML to create my page, so I make a call to haml followed by the name of my HAML document as a symbol. We need to define an ‘index.haml’ page and stick it in a directory called ‘views’ for Sinatra to find it.
1
|
|
1 2 3 4 5 6 |
|
Tough work. If you’re not familiar with HAML, it’s a markup language that is “compiled” into an HTML page. The main difference between HAML and HTML is that HAML parses white space to figure out where closing tags should be placed. So now I want to run my application. I want to run it using ‘rackup,’ so I’d like to define a ‘config.ru’ file in the root of my project directory to do all the work for me.
1 2 3 |
|
Now we run ‘rackup’ from the root of my project directory and see the fruits of my labor. Open up a web browser and enter ‘localhost:9292’ in the address bar. You should see a very simple web page with the content of “LOL HAI” and a title of “Pokemon App.” If you view the source, you’ll see the HTML the HAML was compiled into. Just beautiful, isn’t it? Now if I switch back to my test directory and run Cucumber, what happens? The same result. That’s because I need to tell Capybara what to load before trying to run the tests. I do this by defining an “env.rb” file in a “support” directory of the features directory.
1 2 3 4 5 6 |
|
All I do is require my “app.rb” file which is seemingly forever away and then set the Capybara.app variable to my Application class. Now I run Cucumber and… ‘(4 failed, 17 skipped, 2 passed)’ Two steps passed! Yippee! Now if only the rest passed as well. Looking at my ‘search.feature’ file, I can see that the first ‘When’ step is about typing into the search bar. So my first design decision is what kind of search bar I want. I’ve opted for the fun way out: using the Twitter Bootstrap’s typeahead. The typeahead has functionality to give suggestions while the user types, and the best news is this is already coded for us. Adding the code for my search bar and a search button:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
In the \<head> tag, I include a link to the Bootstrap stylesheet. In the \<body> tag, I include a link to the JQuery and Bootstrap Typeahead JavaScript files remotely so I don’t have to keep track of them. I then add a \<div> tag called “search” and center it on the page. Inside the div tag I create a form whose action sends a POST signal to the “search” action. Inside the form is first the typeahead, then a small submit button. The important parameters in the typeahead are “data-items” and “data-source;” “data-items” tells the JavaScript function how many items to suggest at a time, and “data-source” is an array of data for the JavaScript to search. Notice that my “data-source” uses the Pokemon class created previously, so I need to be able to set up a Mongoid connection to access that data. I’ll make this connection in my “config.ru” file:
1 2 3 4 5 6 7 8 9 10 |
|
I have chosen to extend the Application class in my “config.ru” file to prevent interference with my test setups later. Taking a look at the application would be a good idea, but if I run “rackup” now Mongoid will complain about environment setup. By default, “Mongoid.load!” will try to load the “development” settings, so I need to include a “development” setup in my “mongoid.yml.” For now, it’s going to be identical to my “test” environment setup except for the database name:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
And ensuring some Pokemon are in the “dev” database:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
And requiring the Pokemon model in app.rb:
1 2 3 4 5 6 7 8 9 10 |
|
Finally, run ‘rackup’ from the root of the ‘project’ directory and load up the web application at ‘localhost:9292.’ There’s now a typeahead and a search button in the top center of the page, and typing in the ‘Search’ bar shows up to 10 suggestion Pokemon. Now I’ll return to my Cucumber tests. I need to add a line in the ‘env.rb’ file to set up the Mongoid environment and ensure there are Pokemon in the collection.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now I can run Cucumber and see some jovial results: ‘(4 failed, 9 skipped, 10 passed).’ I now have more steps passing than failing! The root cause of the failures is that there currently is no “search” page; let’s fix that:
1 2 3 4 5 6 7 |
|
I want my search page to have a search bar just like my index page. If I want them to be identical, I want to only have to change that code once. When writing HAML, I can create a ‘layout.haml’ file to act as a base page for my application and move all the text from ‘index.haml.’ I’ll add a ‘=yield’ statement where I want the information from ‘index.haml’ and ‘search.haml’ to be placed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 |
|
At this point, I’ll create a ‘search.haml’ file in the ‘views’ directory, but leave it empty. Running Cucumber now, I get ‘(4 failed, 4 skipped, 15 passed).’ Pretty close! All I fail now is actually seeing the desired information on the page. First I want to get access to the Pokemon searched for: I can do that by parsing the params passed to us in app.rb:
1 2 3 4 5 6 7 8 9 |
|
So now on my search page:
1 2 3 4 5 6 7 8 |
|
I reference the class variable “@pokemon” and access its data. To output Ruby-formatted strings, I use an “=” sign. Running Cucumber, I now see ‘(2 failed, 21 passed).’ 2 of my 4 tests are passing! A trivial amount of investigation reveals that I didn’t deal with the situation where the user types in garbage data. That can mostly be done in the HAML file, but I also want a way to get the bad text the user gave me so they can see what was wrong.
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 |
|
Ruby code with no output is preceded with a ‘-’ and because HAML is space-sensitive, there’s no need to include ‘end’ statements. Now I run Cucumber and… 4 scenarios/23 steps passed! I have a functional web application! Of course, the page itself is somewhat bland, some of the styles could be put in a stylesheet and reused, and the only tests I’ve written are for super-high-level functionality. Those are all problems someone with infinite time would deal with, so I’ll just leave the page is is for now.
The next blog post in this series will be about deploying this application to the web using Heroku.