Rails Testing: What the Scaffolding Gives You for Free

I'm building a Rails app that manages virtual machines running programming puzzles. A few days in, I hit the familiar cycle: wrote code without tests, spent hours manually re-testing the same broken behavior, finally wrote automated tests, and immediately wished I'd done it sooner. Here are the useful pieces I found in my first couple days of Rails testing.

Generated Tests and Fixtures

When I ran rails generate scaffold Puzzle, it produced a full set of tests for the Puzzle controller. That gave me a working template to build from, which was a nice head start.

Every model also gets a corresponding fixture file at test/fixtures/MODEL_NAME.yml, prefilled with sample data for creating test objects. My VirtualMachineInstance fixtures look like this:

manuela: 
   email: [email protected]
rishi:
   email: [email protected]

That lets me create a test object with a single line:

@user = users(:rishi)

Authentication and HTTP Mocking Helpers

I'm using the Devise gem for logins, and it ships with test helpers that let me simulate being logged in when I request a page. That's been very useful in integration tests:

  setup do
    @user = users(:rishi)
    @user.save
    login_as(@user, :scope => :user)
  end

I also have code that calls an external API to launch VM instances. In tests, I obviously don't want to actually spin up instances. Using WebMock.disable_net_connect! prevents Ruby from making any real external requests. If one slips through, WebMock prints an example of the mock code I could write to catch it:

WebMock.disable_net_connect!
stub_request(:get, "https://api.digitalocean.com/v2/account/keys?page=1&per_page=20").
  to_return(status: 200, body: '{"ssh_keys":[],"links":{},"meta":{"total":2}}')

Integration Tests Are Surprisingly Concise

I'd been manually clicking through pages to verify flows, so I was pleasantly surprised at how little code Rails integration tests require. Here's a test that checks a puzzle's instance status is "pending" right after starting it:

test "status is pending right after instance started" do
    get '/puzzles/1/start'
    get '/instances/220816290/status'
    assert_response :success
    assert_equal({"status" => "pending"}, response.parsed_body)
end

The test reads like a description of the user flow rather than a script of HTTP requests and assertions. It has a "magical Ruby" feel to it, but it's working well so far.

The larger takeaway: Rails' ecosystem around testing is mature. The generated scaffolding, fixtures, authentication helpers, and HTTP mocking libraries all fit together, and there's a large body of documentation and community answers when something doesn't work as expected. It's a different experience than learning a less popular framework, where you're often the first person to hit a given problem.