Creating Rails API and unit testing with Minitest

Posted by aehmt on February 23, 2017

The easiest way to create an API in Rails and get it up and running is creating it with scaffold and testing it with Mini Test.

We will use Google-Ads API’s structure to create our API. Google-Ads has Campaings and every campaign has many Adgroups and every Adgroup has many Ads.

First we need to create our application using rails new … command. We need API-only application and we will use Postgres as our database type.

rails new Ads --api —database=postgresql

Once we are done with creating the application. We can scaffold our models and database relations. We will start with Campaigns since it is the topmost parent model in our app.

rails g scaffold campaign name status bid:integer advertising_channel_type

Now we can go ahead and scaffold our Adgroups.

rails g scaffold adgroup name status bid_amount:integer campaign:references

And lastly we can scaffold our Ads.

rails g scaffold ad name adgroup:references headline_part1 headline_part2 description path1 path2 final_urls

The reason why we use …:references instead of ‘ids’ is that references will add belongs_to relationship to our models. But we still need to create has_many relationship in our relevant models.

At this point next step is to serialize the data. Luckily we don’t have to do much Rails created serializers for us only thing we need to do is to include nested associations if we need them.

class CampaignSerializer < ActiveModel::Serializer
  attributes :id, :name, :status, :budget, :advertising_channel_type
  has_many :adgroups, include_nested_associations: true
end


class AdgroupSerializer < ActiveModel::Serializer
  attributes :id, :name, :status, :bid_amount
  has_one :campaign
  has_many :ads, include_nested_associations: true
end


class AdSerializer < ActiveModel::Serializer
  attributes :id, :name, :headline_part1, :headline_part2, :description, :path1, :path2, :final_urls
  has_one :adgroup
end

Now we can start writing our tests using Minitest. We have some hardcoded data as examples.

Campaigns Controller Tests

require 'test_helper'

class CampaignsControllerTest < ActionDispatch::IntegrationTest
  Campaign.destroy_all
  campaigns = [
    {
      :name => "Interplanetary Cruise #%d" % (Time.new.to_f * 1000).to_i,
      :status => 'PAUSED',
      :budget => 44,
      :advertising_channel_type => 'SEARCH'
    },
    {
      :name => "Interplanetary Cruise banner #%d" % (Time.new.to_f * 1000).to_i,
      :status => 'PAUSED',
      :budget => 23,
      :advertising_channel_type => 'DISPLAY'
    }
  ]

      # campaigns.each do |x|
      #   post campaigns_url, params: { campaign: x}, as: :json
      # end
 # setup do
  #   campaigns.each do |x|
  #     Campaign.create(x)
  #   end
  # end

  test "should get index" do
    get campaigns_url, as: :json
    assert_response :success
  end

  test "should create campaign" do
    assert_difference 'Campaign.count', 2 do
    # binding.pry
      campaigns.each do |x|
        post campaigns_url, params: { campaign: x}, as: :json
      end
    end

    assert_response 201
  end

  test "should show campaign" do
    Campaign.all.each do |x|
      get campaign_url(x), as: :json
      assert_response :success
    end
  end

end

Adgroups Controller Tests

require 'test_helper'

class AdgroupsControllerTest < ActionDispatch::IntegrationTest
  Campaign.destroy_all
  Adgroup.destroy_all

  campaigns = [
    {
      :name => "Interplanetary Cruise #%d" % (Time.new.to_f * 1000).to_i,
      :status => 'PAUSED',
      :budget => 44,
      :advertising_channel_type => 'SEARCH'
    },
    {
      :name => "Interplanetary Cruise banner #%d" % (Time.new.to_f * 1000).to_i,
      :status => 'PAUSED',
      :budget => 23,
      :advertising_channel_type => 'DISPLAY'
    }
  ]

  campaigns.each do |x|
    Campaign.create(x)
  end

  ad_groups = [
    {
      :name => "Earth to Mars Cruises #%d" % (Time.new.to_f * 1000).to_i,
      :status => 'ENABLED',
      :campaign_id => Campaign.all.last.id,
      :bid_amount => '8'
    },
    {
      :name => 'Earth to Pluto Cruises #%d' % (Time.new.to_f * 1000).to_i,
      :status => 'ENABLED',
      :campaign_id => Campaign.all.last.id,
      :bid_amount => '4'
    }
  ]

  # setup do
  #   @adgroup = adgroups(:one)
  # end

  test "should get index" do
    get adgroups_url, as: :json
    assert_response :success
  end

  test "should create adgroup" do
    assert_difference 'Adgroup.count', 2 do
      ad_groups.each do |x|
        post adgroups_url, params: { adgroup: x}, as: :json
        # binding.pry
      end
    end

    assert_response 201
  end

  test "should show adgroup" do
    Adgroup.all.each do |x|
      get adgroup_url(x), as: :json
      assert_response :success
    end
  end
end

Ads Controller Tests

require 'test_helper'

class AdsControllerTest < ActionDispatch::IntegrationTest
  expanded_text_ad = [
    {
      :xsi_type => 'ExpandedTextAd',
      :ad_group_id => Adgroup.all.last,
      :headline_part1 => 'Cruise to Mars #%d' % (Time.new.to_f * 1000).to_i,
      :headline_part2 => 'Best Space Cruise Line',
      :description => 'Buy your tickets now!',
      :final_urls => ['http://www.example.com/%d' ],
      :path1 => 'all-inclusive',
      :path2 => 'deals'
    },
    {
      :xsi_type => 'ExpandedTextAd',
      :ad_group_id => Adgroup.all.last,
      :headline_part1 => 'Cruise to Mars #%d' % (Time.new.to_f * 1000).to_i,
      :headline_part2 => 'Best in the galaxy',
      :description => 'Buy your tickets now!',
      :final_urls => ['http://www.example.com/%d' ],
      :path1 => 'all-inclusive',
      :path2 => 'deals'
    }
  ]

  # setup do
  #   @ad = ads(:one)
  # end

  test "should get index" do
    get ads_url, as: :json
    assert_response :success
  end

  test "should create ad" do
    assert_difference 'Ad.count', 2 do
      expanded_text_ad.each do |x|
        post ads_url, params: { ad: x}, as: :json
      end
    end

    assert_response 201
  end

  test "should show ad" do
    Ad.all.each do |x|
      get ad_url(x), as: :json
      assert_response :success
    end
  end

end

If we run tests with rake tests all tests should be passing.

Run options: --seed 55449

# Running:

.........

Finished in 0.671064s, 13.4115 runs/s, 16.3919 assertions/s.

9 runs, 11 assertions, 0 failures, 0 errors, 0 skips

That’s all. Rails lets us create an API and test it with some basic tests in a short period of time.