{"id":2965,"date":"2014-12-16T01:08:01","date_gmt":"2014-12-16T07:08:01","guid":{"rendered":"http:\/\/benincosa.com\/?p=2965"},"modified":"2014-12-16T01:08:01","modified_gmt":"2014-12-16T07:08:01","slug":"rails-api-testing-or-youve-been-doing-it-wrong","status":"publish","type":"post","link":"https:\/\/benincosa.com\/?p=2965","title":{"rendered":"Rails API Testing or &#8220;You&#8217;ve been doing it wrong&#8221;"},"content":{"rendered":"<p>For the last several years I&#8217;ve been developing Ruby on Rails applications. \u00a0Nothing fancy, and most of them just failed projects that didn&#8217;t go anywhere. \u00a0During all this time I&#8217;ve trolled documentation on testing the app, saying &#8220;That would be nice to do one day&#8221;, but I&#8217;ve finally had enough and I want to change my evil ways. I&#8217;m going to start writing test cases with my applications.<\/p>\n<p>To start with, I&#8217;m testing my API. \u00a0I didn&#8217;t even know where to start. \u00a0I watched a <a title=\"How Ryan Tests\" href=\"http:\/\/railscasts.com\/episodes\/275-how-i-test?view=comments\">Railscasts episode<\/a> where the legendary <a href=\"https:\/\/twitter.com\/rbates\">Ryan Bates<\/a> talks about how he tests. \u00a0There were some great comments in there about all these additional plugins. \u00a0It seemed pretty overwhelming to me at first and there was quite a learning curve. \u00a0So I wanted to write some of this down in case there was someone out there like me who was going to start down the path of correcting the error of there ways before 2015 hits.<\/p>\n<h2>Basic Rails Testing<\/h2>\n<p>The first thing to check was <a href=\"http:\/\/guides.rubyonrails.org\/testing.html\">the guide<\/a>. \u00a0Rails has great documentation!<\/p>\n<p>I ran:<\/p>\n<pre class=\"lang:default decode:true\">rake test<\/pre>\n<p>Just to see if I would get any output. \u00a0After all, Ruby generates this for you automatically right? \u00a0Low and behold I got a failure!<\/p>\n<p>Well, first I was missing the test environment, so I put that in the config\/database.yml. \u00a0Easy problem to solve!<\/p>\n<p>Next, my welcome controller gave me errors:<\/p>\n<pre class=\"lang:default decode:true \"> 1) Error:\r\nWelcomeControllerTest#test_should_get_index:\r\nActiveRecord::StatementInvalid: SQLite3::ConstraintException: UNIQUE constraint failed: users.email: INSERT INTO \"users\" (\"created_at\", \"updated_at\", \"id\") VALUES ('2014-12-09 22:38:38', '2014-12-09 22:38:38', 298486374)<\/pre>\n<p>Ok, I already like this. \u00a0Looks like I need to intiate my test database?<\/p>\n<p>Turns out that <a href=\"http:\/\/stackoverflow.com\/questions\/5769758\/adding-index-to-email-column-culprit-for-unit-tests-failing\">Devise is the culprit<\/a>. \u00a0A quick trip to StackOverflow solves the problem. \u00a0I fixed it like they said. Then I ran into <a href=\"http:\/\/stackoverflow.com\/questions\/4308094\/all-ruby-tests-raising-undefined-method-authenticate-for-nilnilclass\">another one with Devise<\/a> that I added to my test\/test_helper.rb file. \u00a0 Its so nice to go over roads that other people have traveled!<\/p>\n<p>Running my test:<\/p>\n<pre class=\"striped:false marking:false ranges:false nums:false nums-toggle:false wrap-toggle:false lang:default decode:true \">$ rake test\r\nRun options: --seed 1932\r\n\r\n# Running tests:\r\n\r\n.\r\n\r\nFinished tests in 3.422119s, 0.2922 tests\/s, 0.2922 assertions\/s.\r\n\r\n1 tests, 1 assertions, 0 failures, 0 errors, 0 skips<\/pre>\n<p>And I finally got tests to work! \u00a0Yay! \u00a0It looks like I&#8217;m doing it\u00a0right.<\/p>\n<p>From here, I\u00a0looked to test other API calls, but all the documentation said that I should probably start looking at rspec. \u00a0Apparently, that&#8217;s how the cool kids are doing it. \u00a0(Or were doing it at some point when they wrote how to do it). \u00a0So after running rake test, that was the last testing I did with the distributed rails testing.<\/p>\n<h2>RSpec<\/h2>\n<p>This is the latest hotness in testing that I could find in my research. \u00a0Pretty much everybody seems to be using it. \u00a0I edited my Gemfile and added rspec-rails. \u00a0I also finally started grouping things so I wouldn&#8217;t install these unnecessary gems on my production servers. \u00a0Spoiler alert: \u00a0My completed Gemfile looks like the below:<\/p>\n<pre class=\"lang:default decode:true\" title=\"Gemfile\">group :test, :development do\r\n  gem 'capistrano', group: :development\r\n  gem 'capistrano-rails', group: :development\r\n  gem 'capistrano-bundler', group: :development\r\n  gem 'capistrano-rvm', group: :development\r\n  gem 'rspec-rails'\r\n  gem 'factory_girl_rails'\r\n  # for some quick tests\r\n  gem 'shoulda-matchers', require: false\r\n  # for fake names \r\n  gem 'ffaker'\r\nend<\/pre>\n<p>As you can see, I added a few more gems after rspec-rails, but I&#8217;ll get into those in a second.<\/p>\n<p>After doing bundle install I ran:<\/p>\n<pre class=\"lang:default decode:true \">rails generate rspec:install\r\n      create  .rspec\r\n      create  spec\r\n      create  spec\/spec_helper.rb\r\n      create  spec\/rails_helper.rb<\/pre>\n<p>Now to test we run<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">bundle exec rspec<\/span><\/p>\n<p>Ok, no tests to do yet! \u00a0Now to get to work!<\/p>\n<h2>Factory Girl &amp; FFaker and other setup<\/h2>\n<p>The next step was to put Factory Girl. \u00a0Once again, <a href=\"http:\/\/railscasts.com\/episodes\/158-factories-not-fixtures-revised\">Ryan Bates explains why Factory Girl is preferred over Fixtures<\/a>. \u00a0I went back and added that to my Gemfile along with <a href=\"https:\/\/github.com\/EmmanuelOga\/ffaker\">ffaker<\/a> because I saw some cool things in that gem. \u00a0(The one thing not cool about ffaker was the documentation, but the code was easy enough to read.<\/p>\n<p>Next, I modified config\/application as specified in this <a href=\"http:\/\/everydayrails.com\/2012\/03\/12\/testing-series-rspec-setup.html\">blog entry<\/a>.<\/p>\n<pre class=\"lang:ruby decode:true \" title=\"config\/application.rb\">require File.expand_path('..\/boot', __FILE__)\r\n\r\nrequire 'rails\/all'\r\n\r\n# Require the gems listed in Gemfile, including any gems\r\n# you've limited to :test, :development, or :production.\r\nBundler.require(:default, Rails.env)\r\n\r\nENV.update YAML.load(File.read(File.expand_path('..\/application.yml', __FILE__)))\r\nmodule MyApp\r\n  class Application &lt; Rails::Application\r\n    config.i18n.enforce_available_locales = true\r\n    config.generators do |g| \r\n      g.test_framework :rspec,\r\n        :fixtures =&gt; true,\r\n        :view_specs =&gt; false,\r\n        :helper_specs =&gt; false,\r\n        :routing_specs =&gt; false,\r\n        :controller_specs =&gt; true,\r\n        :request_specs =&gt; true\r\n      g.fixture_replacement :factory_girl, :dir =&gt; \"spec\/factories\" \r\n    end \r\n  end \r\n\r\nend<\/pre>\n<p>I also had to add these modules into the rest of the environment. \u00a0I changed the spec\/rails_helper.rb to have the below. \u00a0Everything else stayed the same:<\/p>\n<pre class=\"lang:default decode:true\">Dir[Rails.root.join(\"spec\/support\/**\/*.rb\")].each { |f| require f }\r\nrequire 'shoulda-matchers'\r\nrequire 'ffaker'<\/pre>\n<p>Then I added the directory:<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline\">mkdir spec\/support<\/span><\/p>\n<p>I added the file\u00a0<span class=\"lang:default decode:true  crayon-inline\">spec\/support\/devise.rb<\/span><\/p>\n<pre class=\"lang:ruby decode:true\">RSpec.configure do |config|\r\n  config.include Devise::TestHelpers, :type =&gt; :controller\r\nend<\/pre>\n<p>as well as the file\u00a0<span class=\"lang:default decode:true  crayon-inline \">spec\/support\/factory_girl.rb<\/span><\/p>\n<pre class=\"lang:ruby decode:true \">RSpec.configure do |config|\r\n  config.include FactoryGirl::Syntax::Methods\r\nend<\/pre>\n<p>That has all my extra libraries used for my tests.<\/p>\n<p>Lastly, I setup the test database<\/p>\n<p class=\"\"><span class=\"lang:default decode:true  crayon-inline\">rake db:test:prepare<\/span><\/p>\n<h2 class=\"\">A basic Tests<\/h2>\n<p>Now to set up some tests. \u00a0I\u00a0thought it best to start off simple with a static page:<\/p>\n<p class=\"\"><span class=\"lang:default decode:true  crayon-inline\">rails g rspec:controller welcome<\/span><\/p>\n<p class=\"\">This is the root of the homepage. \u00a0Following the <a href=\"https:\/\/github.com\/rspec\/rspec-rails\">documentation<\/a>, I added some simple tests for the welcome page:<\/p>\n<pre class=\"lang:ruby decode:true\" title=\"spec\/controller\/welcome_controller_spec.rb\"># this is spec\/controller\/welcome_controller_spec.rb\r\nrequire 'rails_helper'\r\n\r\nRSpec.describe WelcomeController, :type =&gt; :controller do\r\n  describe \"GET #index\" do  \r\n    it \"responds successfully with an HTTP 200 status code\" do\r\n      get :index\r\n      expect(response).to be_success\r\n      expect(response).to have_http_status(200)\r\n    end \r\n\r\n    it \"renders the index template\" do  \r\n      get :index\r\n      expect(response).to render_template(\"index\")\r\n    end \r\n\r\n  end \r\nend<\/pre>\n<p>I ran <span class=\"lang:default decode:true  crayon-inline \">bundle exec rspec<\/span>\u00a0 and\u00a0it worked. \u00a0(Though not at first, as I had to figure out how to configure everything like I set up above. )<\/p>\n<h2>Testing User Model<\/h2>\n<p><span class=\"lang:default decode:true  crayon-inline\">rails generate rspec:model user<\/span><\/p>\n<p>Since I already have a user model. \u00a0The list of spec modules to add are listed <a href=\"https:\/\/www.relishapp.com\/rspec\/rspec-rails\/docs\/generators\">here<\/a>.<\/p>\n<p>Since we have a model we are testing, we need to generate the fixture for it. \u00a0Here&#8217;s how I made it work with my Devise implementation:<\/p>\n<p>spec\/factories\/user.rb<\/p>\n<pre class=\"lang:ruby decode:true\" title=\"spec\/factories\/user.rb\">FactoryGirl.define do\r\n  factory :user , :class =&gt; User do  \r\n    username { Faker::Internet.user_name }\r\n    password \"foobar123\"\r\n    password_confirmation { |u| u.password }\r\n    email { Faker::Internet.email }\r\n  end \r\nend<\/pre>\n<p>The part that was most important that stumped me for a while was not putting the { } around Faker::Internet.email. \u00a0Since my tests tests for unique emails, it kept failing. \u00a0Putting the {} around Faker::Internet.email made sure it was unique on each call.<\/p>\n<p>There&#8217;s a lot of documentation on cleaning up the database by using the database_cleaner gem. \u00a0I&#8217;m not using it right now. \u00a0ffaker generates all kinds of new things for me, so I don&#8217;t worry about it. \u00a0I suppose that the database would need to be initialized though from time to time.<\/p>\n<p>This could be accomplished with:<\/p>\n<pre class=\"striped:false marking:false ranges:false nums:false nums-toggle:false wrap-toggle:false lang:default decode:true\">RAILS_ENV=test rake db:drop db:create\r\nrake db:migrate RAILS_ENV=test<\/pre>\n<p>Next I added the user model test<\/p>\n<p>spec\/models\/user_spec.rb<\/p>\n<pre class=\"lang:ruby decode:true\">require 'rails_helper'\r\n\r\nRSpec.describe User, :type =&gt; :model  do  \r\n\r\n  before {\r\n    @user = FactoryGirl.build(:user)\r\n  }\r\n\r\n  subject { @user }\r\n\r\n  it { should be_valid }\r\n  it { should respond_to(:email) }\r\n  it { should respond_to(:password) }\r\n  it { should respond_to(:password_confirmation) }\r\n\r\n  it { should validate_presence_of(:email) }\r\n  it { should validate_uniqueness_of(:email) }\r\n  it { should validate_confirmation_of(:password) }\r\n  it { should allow_value('foo@domain.com').for(:email) }\r\n\r\n  it { should respond_to(:authentication_token) }\r\n  it { should validate_uniqueness_of(:authentication_token) }\r\n\r\nend<\/pre>\n<p>This uses the shoulda-matches gem quite heavily and seems to be a good start to testing my user model. \u00a0Unit test check!<\/p>\n<pre class=\"striped:false marking:false ranges:false nums:false nums-toggle:false wrap-toggle:false lang:default decode:true \">$ bundle exec rspec spec\/models\/user_spec.rb \r\n..........\r\n\r\nFinished in 0.12633 seconds (files took 2.34 seconds to load)\r\n10 examples, 0 failures<\/pre>\n<h2>\u00a0Testing the API Controller<\/h2>\n<p>Next, I wanted to check the API for when users authenticate. \u00a0The way my API works (and the way I assume most work this way) is that the user will send a username (or email) and password and from that the application will send back an API token. \u00a0This makes subsequent calls stateless. \u00a0So I&#8217;ll test my session login \u00a0controller:<\/p>\n<p><span class=\"lang:default decode:true crayon-inline\">rails g rspec:controller api\/v1\/sessions<\/span><\/p>\n<p class=\"\">\u00a0I was happy to see it created\u00a0<span class=\"lang:default decode:true  crayon-inline\">spec\/controllers\/api\/v1\/sessions_controller_spec.rb<\/span>\u00a0\u00a0just like how I have my API!<\/p>\n<p>Here&#8217;s the first\u00a0version of my working sessions_controller_spec.rb file:<\/p>\n<pre class=\"lang:ruby decode:true \" title=\"spec\/controllers\/api\/v1\/sessions_controller_spec.rb\">require 'rails_helper'\r\n\r\nRSpec.describe Api::V1::SessionsController, :type =&gt; :controller do\r\n\r\n  describe \"POST #create\" do\r\n    before(:each) do  \r\n      # set this as recommended by Devise so tests pass. \r\n      @request.env[\"devise.mapping\"] = Devise.mappings[:user]\r\n      @user = FactoryGirl.create :user\r\n    end \r\n\r\n    context \"when the credentials are correct\" do  \r\n      before(:each) do\r\n        credentials = { :user =&gt; {email: @user.email, password: @user.password } } \r\n        post :create, credentials\r\n      end \r\n     \r\n      it \"returns response status 200 given credentials\" do\r\n        #puts @user\r\n        #@user.reload\r\n        expect(response.status).to eq(200)\r\n      end \r\n     \r\n      it \"returns the user authentication token\" do  \r\n        result = JSON.parse(response.body)\r\n        expect(result['auth_token']).to eq @user.authentication_token\r\n      end \r\n\r\n      it \"returns the username\" do\r\n        result = JSON.parse(response.body)\r\n        expect(result['username']).to eq @user.username\r\n      end \r\n\r\n      it \"returns the email\" do\r\n        result = JSON.parse(response.body)\r\n        expect(result['email']).to eq @user.email\r\n      end \r\n    end \r\n  end \r\nend<\/pre>\n<p>Running this test:<\/p>\n<pre class=\"striped:false marking:false ranges:false nums:false nums-toggle:false wrap-toggle:false lang:default decode:true \">$ bundle exec rspec spec\/controllers\/api\/v1\/sessions_controller_spec.rb \r\n....\r\n\r\nFinished in 0.10525 seconds (files took 2.18 seconds to load)\r\n4 examples, 0 failures<\/pre>\n<p>Wow! \u00a0I feel like a real hipster programmer now! \u00a0Testing!<\/p>\n<h2>More Tests<\/h2>\n<p>This is just the beginning. \u00a0I am now a convert and subscribe to the\u00a0theory that <a href=\"http:\/\/programmers.stackexchange.com\/questions\/185886\/we-are-spending-more-time-implementing-functional-test-than-implementing-the-sys\">you should spend about half of your time writing test cases<\/a>. \u00a0It pays off in the long run. \u00a0I have more to go, but from now on with each line of code I write I&#8217;ll be writing test cases. \u00a0It seems that I still have a lot of catching up to do with the current system.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p class=\"\">\n","protected":false},"excerpt":{"rendered":"<p>For the last several years I&#8217;ve been developing Ruby on Rails applications. \u00a0Nothing fancy, and most of them just failed projects that didn&#8217;t go anywhere. \u00a0During all this time I&#8217;ve trolled documentation on testing the app, saying &#8220;That would be nice to do one day&#8221;, but I&#8217;ve finally had enough and I want to change&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[49,1],"tags":[602,58,601],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/2965"}],"collection":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2965"}],"version-history":[{"count":1,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/2965\/revisions"}],"predecessor-version":[{"id":2995,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/2965\/revisions\/2995"}],"wp:attachment":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2965"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2965"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2965"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}