The Ruby community is well known for its adoption of good testing practices. For example, the PHP community is not even close the same level of understanding of the importance of testing in every stage of app development. Moreover, the average PHP developer does not write nearly as many tests as the average Ruby developer does. One of the secrets of Ruby’s testing success is its great tools. Ruby testing tools provide powerful syntax and features, made possible by metaprogramming and DSLs.

Let’s not pat ourselves on the back too quickly, though. As always, great power comes with great responsibility. These tools are a double-edged sword. You need to recognize the good parts and the not-so-good parts, so as not to hurt yourself.

Testing rules:

Tests should be simple and obvious. They should not require testing, themselves.

Each test should do only one thing.

Tests should have clear assertions.

Tests should be predictable and repeatable, producing the same result on each run.

Unit tests

Unit tests cover code-level logic. It instantiates an object, and makes assertions on the result of a method or function call. There are two popular unit testing solutions in Ruby: Minitest and RSpec.

RSpec is a very popular gem that provides an expressive DSL for all aspects of testing. It allows writing test in a BDD “expects” style. Its assertions tend to be human-readable, and it has lots of special extensions. However, its main advantage is also its main drawback – you need to learn a large DSL and assertion syntax. RSpec extensions make this worse, requiring you to put in quite a lot of effort to learn everything. RSpec has community-backed best practices that can be accessed on betterspecs.

RSpec . describe "Using an array as a stack" do def build_stack [] end before ( :example ) do @stack = build_stack end it 'is initially empty' do expect ( @stack ). to be_empty end context "after an item has been pushed" do before ( :example ) do @stack . push :item end it 'allows the pushed item to be popped' do expect ( @stack . pop ). to eq ( :item ) end end end

Minitest became a part of the Ruby standard library, and that is why it is preferable for testing gems and libs, as it does not require additional dependencies. It provides a very small assertions interface, that is easy to learn and adopt. The main advantage of Minitest is that tests are just POROs. That is why you do not need to learn anything new to structure your tests – it is just pure Ruby. Use the same techniques as in your other code. Code initialization and calls are almost identical to real usage. IMHO Minitest is the better choice for writing simple, clear and predictable tests.

class TestMeme < Minitest :: Test def setup @meme = Meme . new end def test_that_kitty_can_eat assert_equal "OHAI!" , @meme . i_can_has_cheezburger? end def test_that_it_will_not_blend refute_match /^no/i , @meme . will_it_blend? end def test_that_will_be_skipped skip "test this later" end end

Minitest also provides a Spec style, which has a lot of RSpec syntactic sugar, but still has a dramatically simpler code base (when you look inside Minitest gem code).

describe Meme do before do @meme = Meme . new end describe "when asked about cheeseburgers" do it "must respond positively" do @meme . i_can_has_cheezburger? . must_equal "OHAI!" end end describe "when asked about blending possibilities" do it "won't say no" do @meme . will_it_blend? . wont_match /^no/i end end end

Stay consistent, don’t write tests in different styles with Minitest. Choose the one you like the most, and write all tests in it. But remember that using Spec style with Minitest reduces its advantages, like normal Ruby syntax and usage of normal OOP techniques.

Test Behavior, not Configuration

The shoulda-matchers gem is very popular for testing aspects of ActiveRecord model configuration, like has_one .

class Person < ActiveRecord :: Base has_one :partner end class PersonTest < ActiveSupport :: TestCase should have_one ( :partner ) end

But why do we need to test indirect signs of expected behavior instead of testing the behavior directly?

We could better write behavior tests like this:

class PersonTest < Minitest :: Test def test_has_parter person = Person . new partner = Partner . new assert_equal partner , person . partner end end

The test above will continue passing, regardless of any changes to the implementation of person.partner . This is exactly how your app expects the Person class to behave, so why should your tests be different, and rely upon the internal implementation?

Integration tests

Ruby allows you to write integration tests. These emulate the real interaction between the web app and the browser. They usually contain a set of commands to click on links, visit certain pages, fill in form fields, and validate the results of these actions.

Integration tests can be written using the Capybara framework. Basically, it acts as a “fake browser,” making requests to your Rack app and parsing the responses, but is not capable of running JavaScript. To fully emulate an end-user browser, you need to use the Poltergeist or Capybara Webkit add-ons. They run the same commands inside a headless Webkit browser. Of course, this incurs a speed penalty. Tests with JS run slower than tests without JS. You need to be aware of that, and activate JS per test, only in the tests that require it.

Understand that integration tests should not make assertions against the internal state of the web app. They should not make assertions on code variables, etc. They should only assert external output, like the response body and HTTP status codes.

Also, do not overuse integration tests. Keep in mind that in any (ANY!) case, integration tests are an order of magnitude slower than unit tests. When you write bad quality code – e.g. putting a lot of logic into Controllers – the only way to test it is to visit the corresponding page. You will need to execute the whole app request cycle to test only tiny things. That should encourage you to remove logic from the controller, put it into separate classes, and just call the new classes from the controller. That way, you can test 90% of the logic via unit tests (which are fast) and make only a few “smoke tests” on the controller action (to test success/failure scenarios, but not every part of the business logic).

Test data: Fixtures vs Factories

Stub external services call

Your tests should not depend on the availability of external services. Ideally, you should be able to run all tests without Internet access, but that doesn’t mean that external integrations should not be tested. If your app makes a request to an external service, this request should be stubbed. Instead of making a real call, it should “pretend” and return a ready-made response (there could be a couple of different responses, to test success/failure). Gems like Webmock and VCR can help you to catch real responses and then reuse them for subsequent test runs, or to write your own response content.

Stub request with Webmock:

stub_request ( :post , "www.example.com" ) . with ( :query => { 'user' => 'Ievgen' }) . to_return ( :body => "Nice work!" )

Catch requests and return a “fake” response with VCR:

VCR . use_cassette ( "synopsis" ) do response = Net :: HTTP . get_response ( URI ( 'http://www.iana.org/domains/reserved' )) assert_match /Example domains/ , response . body end

Speed-up test

To achieve fast tests, stub behavior, heavy computations, and external web calls, in addition to mocking dependencies. You can also run tests in parallel – another reason to keep them completely independent from one another.

Learn Tests Design

To write cost-efficient and effort-efficient tests – tests that should not be rewritten from scratch after any small refactoring – you should design your tests almost as well as you design your other code.