Teeps

Getting Started With Test Driven Development

19 January 2018 by Chayel Heinsen

Experienced development teams understand the importance of TDD or Test Driven Development. If you're unfamiliar with the concept, don't fret. Here we will explain how writing tests can empower both engineers and organizations to build better software products.

Unit Tests are used to ensure that the code you write does what it is intended to do. The process looks like this; engineers make a list of requirements, write a Unit Test for a specific requirement and then modify the codebase to pass that test. As you build out your application, changes to the code may cause a test to fail. Your failed test will help you track down the error to the exact line of code and help you correct the issue faster.

For our clients, it allows their project budgets to stretch further. We aren’t wasting time sifting through thousands of lines of code wondering why a bug exists. Our tests alert us of issues, we fix them quickly and get back to building features.

Let's walk through an example of writing tests for posting a photo and caption in a Rails project.

User Stories

  • As a user, I want to be able to submit a post with a photo and a caption
  • As a user, I want to be able to view posts on my feed

Now that we have our requirements, let's write our tests. We will be using RSpec and FactoryBot. I recommend you check them out for your Rails app.Our first story clearly states that a post requires a photo and a caption. We can start with the model.

Post Model

  • Should be invalid without photo
  • Should be invalid without a captionWe first start with building a factory so that we have an instance of Post to test with.
# specs/factories.rb

FactoryBot.define do
  factory :post do
    photo create(:photo)
    caption "This is a test caption"
    user create(:user)
  end

  factory :user do
    name "Admin"
  end

  factory :photo do
    url "https://example.com/photo"
  end
end

With the factory built, we can now test our Post model. We will start with some validation helpers. These helpers will allow us to write our validation tests easier.

# specs/support/helpers/validation_helpers.rb
require "rails_helper"

module ValidationHelpers
  def build_and_validate(*args)
    FactoryBot.build(*args).tap(&:validate)
  end

  def build_stubbed_and_validate(*args)
    FactoryBot.build_stubbed(*args).tap(&:validate)
  end

  def create_and_validate(*args)
    FactoryBot.create(*args).tap(&:validate)
  end
end

RSpec.configure do |config|
  config.include ValidationHelpers
end

And now our Post model tests. We are stating that the factory must be valid, that is that all of our validations pass. We also state that a post is invalid without a caption, photo, or user.

# specs/models/post_spec.rb

RSpec.describe Post, type: :model do
  context "validation" do
    it "has a valid factory" do
      expect(create(:post)).to be_valid
    end

    it "is invalid without a user" do
      post = build_stubbed_and_validate(:post, user: nil)
      expect(post.errors).to have_key(:user_id)
    end

    it "is invalid without a caption" do
      post = build_stubbed_and_validate(:post, token: nil)
      expect(post.errors).to have_key(:caption)
    end

    it "is invalid without a photo" do
      post = build_stubbed_and_validate(:post, photo: nil)
      expect(post.errors).to have_key(:photo)
    end
  end
end

Running these tests will produce errors, such as, NameError: uninitialized constant Post. This occurs because we obviously haven't written that code yet. So let's fix these errors! Let's assume that the User and Photo models already exist. We can start with creating the Post class.

# app/models/post.rb

class Post < ApplicationRecord
end

This fixes the NameError: uninitialized constant Post error. Now we can go ahead and add the code to fix the validation errors from our tests. Here, we add our validations for a Post.

# app/models/post.rb

class Post < ApplicationRecord
  validates :caption, presence: :true
  validates :photo, presence: :true
  validates :user, presence: :true
end

Now that everything for our model is built, we can run the tests again and the tests will pass!Now that we've built and tested our model, let's test our routes.

Post Routes Tests

  • POST /posts should accept a photo and a captionStarting with posting, we test that with valid parameters, the post is created, otherwise an error is rendered.
# spec/requests/post_spec.rb

RSpec.describe "Posts", type: :request do
  let(:post) { create(:post) }
  let(:post_params) { attributes_for(:post) }

  describe "POST /posts" do
    before do
      post "/posts", params: { post: post_params }
    end

    context "when given valid params" do
      it "creates a new post" do
        expect(response).to be_created
      end
    end

    context "when given an invalid caption param" do
      let(:post_params) { attributes_for(:post, caption: nil) }

      it "renders a 422" do
        expect(response.status).to eq(422)
      end
    end

    context "when given an invalid photo param" do
      let(:post_params) { attributes_for(:post, photo: nil) }

      it "renders a 422" do
        expect(response.status).to eq(422)
      end
    end
  end
end

Now that our tests are written, let's write the code so that these will pass.

# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  def posts
    post = Post.new(post_params)

    if post.save
      render json: post, status: :created
    else
      render json: post.errors, status: :unprocessable_entity
    end
  end

  private

  def post_params
    params.require(:post).permit(:caption, :photo)
  end
end

Running the full test suite will now show everything green. You're good to go!

The process allows developers to focus on writing efficient code and designing simple, yet highly effective architecture. In a future blog post, we’ll show you how to constantly run your suite of tests using continuous integration and CircleCI.


Need help bringing your mobile project to life? Our in-house team of designers and developers has built apps for over 70 clients. Get in touch with us.

Let's build your next big thing.

Contact Us