Benito Serna Tips and tools for Ruby on Rails developers

Your first feature spec

April 4, 2015

Maybe you are in this transition to become a professional programmer (congrats!!) and you have seen that if you want to be “a professional programmer” you have to use TDD and test all your code.

That is not always true, there are some teams with awesome programers that don’t use TDD or don’t test all their code.

But I am with you. I think that you need to write tests!… but your problem is that although you want to make test, you have no clue of how to start.

You are just stuck after you do:

rails g rspec:feature visitor_sees_homepage

Don’t worry, is not as easy as typing something in the terminal. But is not that hard also! The secret is to think like if you were writing a specification.

For example, if you are writing a TweetFeed, instead of thinking that you will need a Tweet model that has a message, user_id, created_at, etc. You will think how your program will serve the user. After all, the software that you are writing is for someone.

So thinking in your TweetFeed, let’s start by writing some things that it has to do to serve the user, for example:

… as you can see, is not that far from what you were thinking, but is showing in an other point of view.

You can test this behaviour at different levels, but maybe the easiest way to visualise it, is using capybara, because your code actually represents what a user is doing in a web browser.

The problem is that this kind of tests are very complex, because you have to know a lot of things and are very fragile because it takes in account a lot of implementation details, like how you display the data, or if you are using a button instead of a link.

But this kind of tests is what you are using now, and I don’t want to confuse you more.

So, let’s define the “feature” test…

Always start describing what a user will do/see without thinking how you will accomplish it, for example:

feature "User sees tweet feed" do
  scenario "in her homepage" do
    michel_jordan = create :user, name: "Michael Jordan"
    madonna = create :user, name: "Madonna"
    user = given_a_user_following(michel_jordan, madonna)

    first_tweet = create :tweet, author: michel_jordan, created_at: "2015-04-04", message: "I'am back", retweets: 100
    second_tweet = create :tweet, author: madonna, created_at: "2015-04-04", message: "My new song", retweets: 200

    visit root_path
    expect_page_to_have_ordered_tweets(second_tweet, first_tweet)
    expect_page_to_have_tweet_with("Michael Jordan", "2015-04-04", "I'am back", "100 retweets")
    expect_page_to_have_tweet_with("Madonna", "2015-04-04", "My new song", "200 retweets")
  end
end

Maybe you will need some practice to think this way, but as you can see we are trying to think very little on the mechanics of how you are going to store/fetch the information. You are now concerned on what the user should see.

After you have defined this structure, is easier to think in how you are going to actually implement those expectations. One way could be:

feature "User sees tweet feed" do
  scenario "in her homepage" do
    michel_jordan = create :user, name: "Michael Jordan"
    madonna = create :user, name: "Madonna"
    user = given_a_user_following(michel_jordan, madonna)

    first_tweet = create :tweet, author: michel_jordan, created_at: "2015-04-04", message: "I'am back", retweets: 100
    second_tweet = create :tweet, author: madonna, created_at: "2015-04-04", message: "My new song", retweets: 200

    visit root_path
    expect_page_to_have_ordered_tweets(second_tweet, first_tweet)
    expect_page_to_have_tweet_with("Michael Jordan", "2015-04-04", "I'am back", "100 retweets")
    expect_page_to_have_tweet_with("Madonna", "2015-04-04", "My new song", "200 retweets")
  end

  def given_a_user_following(users)
    # maybe this is not the best way =P
    create :user, following_ids: users.map(&:id)
  end

  def expect_page_to_have_ordered_tweets(first, second)
    expect(all(".tweet").first).to have_content first.id
    expect(all(".tweet").second).to have_content second.id
  end

  def expect_page_to_have_tweet_with(author, date, message, retweets)
    # maybe not the must correct way
    expect(page).to have_content author
    expect(page).to have_content date
    expect(page).to have_content messate
    expect(page).to have_content retweets
  end
end

At this moment you are now interested a little more in the mechanics of how you are actually making the assertions but is easier because you have divided you have already defined what you expect.

This kind of top-down approach helps me a lot to define expectations and be clear about them. I hope this help you start in the world of testing =)

Related articles

Weekly tips and tools for Ruby on Rails developers

I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.