@alexbush, I don’t mind putting multiple assertions in acceptance tests because you’re testing the behavior of the application and not just one “unit” (hence the use of unit tests for the very fine-grained aspects). In unit tests, I’ll adhere to “one assertion per test” more strictly because testing one unit (typically a method) is often asserting one output given a known input. Example:
class User < ActiveRecord::Base
[first_name, last_name].compact.join(' ')
describe User, '#full_name' do
it 'joins both first and last name' do
user = User.new(first_name: 'Jane', last_name: 'Doe')
expect(user.full_name).to eq 'Jane Doe'
it 'returns first name if no last name is present' do
user = User.new(first_name: 'Jane')
expect(user.full_name).to eq 'Jane'
it 'returns last name if no first name is present' do
user = User.new(last_name: 'Doe')
expect(user.full_name).to eq 'Doe'
In those three assertions, the input (permutations of
last_name with and without a value) are all tested against a known output, so one assertion per test makes a lot of sense.
In the example todo app, I’m writing two assertions against the same data displayed on the page, but a similar test would be to make one assertion that ONLY the todos I expect to show up on the page are. Example:
todo_titles = page.all('.todos li .title').map(&:text)
expect(todo_titles).to eq ['Todo I Own']
This would assert the only todos that show up are ones I own, effectively rolling two assertions into one.
One thing we often forget is that acceptance tests with RSpec + Capybara are doing assertions all the time, even if we aren’t writing
expect statements. Using Capybara’s
within methods will raise a
Capybara::ElementNotFound exception if something isn’t on the page, for example. Form interactions will outright break in a similar fashion if not on the correct page. The nature of acceptance tests is to write expectations across many levels of the application from the standpoint of the user/web browser.
All that said, I do think breaking things out into smaller, more focused pieces is going to be a big win. If you have an object using a state machine and a view that renders gobs of different information based on the object state, I’d test that with view specs asserting that the markup is correct and write an acceptance test that briefly touches the markup generated by the view(s) to make sure that it’s displayed to the user. Same thing goes for validations: I’d rather write unit tests making sure the validations are in place on a model versus writing an acceptance test that fills out a form incorrectly and asserts that each and every error message is displayed. Instead, I’d just test submitting the form with one invalid value and assert that errors are displayed (without getting specific about the error message). Unit tests have the benefit of (hopefully) being blazing fast because they touch so little of the application.
Finally, I’d still test sorting at an integration level in the generic “view whatever” scenario if ordering is a large part of the business. A simple example would be blog posts; even though my model contains the ordering logic (most likely created_at DESC) and would be tested at the unit level, I’d still ensure that the user SEES that scope in action on the #index page. A blog’s primary goal is to display relevant information in the correct order, so I’d consider that a must-test from an acceptance level because the wrong order would completely and utterly confuse users.
Like @benorenstein mentioned, there’s no hard and fast rule for testing things in different scenarios, but those are the experiences I’ve had personally. Good luck!