Basic Rails Testing
The first thing to check was the guide. Rails has great documentation! I ran:rake testJust to see if I would get any output. After all, Ruby generates this for you automatically right? Low and behold I got a failure! Well, first I was missing the test environment, so I put that in the config/database.yml. Easy problem to solve! Next, my welcome controller gave me errors:
1) Error:
WelcomeControllerTest#test_should_get_index:
ActiveRecord::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)
Ok, I already like this. Looks like I need to intiate my test database?
Turns out that Devise is the culprit. A quick trip to StackOverflow solves the problem. I fixed it like they said. Then I ran into another one with Devise that I added to my test/test_helper.rb file. Its so nice to go over roads that other people have traveled!
Running my test:
$ rake test Run options: --seed 1932 # Running tests: . Finished tests in 3.422119s, 0.2922 tests/s, 0.2922 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skipsAnd I finally got tests to work! Yay! It looks like I'm doing it right. From here, I looked to test other API calls, but all the documentation said that I should probably start looking at rspec. Apparently, that's how the cool kids are doing it. (Or were doing it at some point when they wrote how to do it). So after running rake test, that was the last testing I did with the distributed rails testing.
RSpec
This is the latest hotness in testing that I could find in my research. Pretty much everybody seems to be using it. I edited my Gemfile and added rspec-rails. I also finally started grouping things so I wouldn't install these unnecessary gems on my production servers. Spoiler alert: My completed Gemfile looks like the below:group :test, :development do gem 'capistrano', group: :development gem 'capistrano-rails', group: :development gem 'capistrano-bundler', group: :development gem 'capistrano-rvm', group: :development gem 'rspec-rails' gem 'factory_girl_rails' # for some quick tests gem 'shoulda-matchers', require: false # for fake names gem 'ffaker' endAs you can see, I added a few more gems after rspec-rails, but I'll get into those in a second. After doing bundle install I ran:
rails generate rspec:install
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
Now to test we run
bundle exec rspec
Ok, no tests to do yet! Now to get to work!
Factory Girl & FFaker and other setup
The next step was to put Factory Girl. Once again, Ryan Bates explains why Factory Girl is preferred over Fixtures. I went back and added that to my Gemfile along with ffaker because I saw some cool things in that gem. (The one thing not cool about ffaker was the documentation, but the code was easy enough to read. Next, I modified config/application as specified in this blog entry.require File.expand_path('../boot', __FILE__)
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env)
ENV.update YAML.load(File.read(File.expand_path('../application.yml', __FILE__)))
module MyApp
class Application < Rails::Application
config.i18n.enforce_available_locales = true
config.generators do |g|
g.test_framework :rspec,
:fixtures => true,
:view_specs => false,
:helper_specs => false,
:routing_specs => false,
:controller_specs => true,
:request_specs => true
g.fixture_replacement :factory_girl, :dir => "spec/factories"
end
end
end
I also had to add these modules into the rest of the environment. I changed the spec/rails_helper.rb to have the below. Everything else stayed the same:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
require 'shoulda-matchers'
require 'ffaker'
Then I added the directory:
mkdir spec/support
I added the file spec/support/devise.rb
RSpec.configure do |config| config.include Devise::TestHelpers, :type => :controller endas well as the file spec/support/factory_girl.rb
RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods endThat has all my extra libraries used for my tests. Lastly, I setup the test database
rake db:test:prepare
A basic Tests
Now to set up some tests. I thought it best to start off simple with a static page:rails g rspec:controller welcome
This is the root of the homepage. Following the documentation, I added some simple tests for the welcome page:
# this is spec/controller/welcome_controller_spec.rb
require 'rails_helper'
RSpec.describe WelcomeController, :type => :controller do
describe "GET #index" do
it "responds successfully with an HTTP 200 status code" do
get :index
expect(response).to be_success
expect(response).to have_http_status(200)
end
it "renders the index template" do
get :index
expect(response).to render_template("index")
end
end
end
I ran bundle exec rspec and it worked. (Though not at first, as I had to figure out how to configure everything like I set up above. )
Testing User Model
rails generate rspec:model user Since I already have a user model. The list of spec modules to add are listed here. Since we have a model we are testing, we need to generate the fixture for it. Here's how I made it work with my Devise implementation: spec/factories/user.rbFactoryGirl.define do
factory :user , :class => User do
username { Faker::Internet.user_name }
password "foobar123"
password_confirmation { |u| u.password }
email { Faker::Internet.email }
end
end
The part that was most important that stumped me for a while was not putting the { } around Faker::Internet.email. Since my tests tests for unique emails, it kept failing. Putting the {} around Faker::Internet.email made sure it was unique on each call.
There's a lot of documentation on cleaning up the database by using the database_cleaner gem. I'm not using it right now. ffaker generates all kinds of new things for me, so I don't worry about it. I suppose that the database would need to be initialized though from time to time.
This could be accomplished with:
RAILS_ENV=test rake db:drop db:create rake db:migrate RAILS_ENV=testNext I added the user model test spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, :type => :model do
before {
@user = FactoryGirl.build(:user)
}
subject { @user }
it { should be_valid }
it { should respond_to(:email) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should validate_confirmation_of(:password) }
it { should allow_value('foo@domain.com').for(:email) }
it { should respond_to(:authentication_token) }
it { should validate_uniqueness_of(:authentication_token) }
end
This uses the shoulda-matches gem quite heavily and seems to be a good start to testing my user model. Unit test check!
$ bundle exec rspec spec/models/user_spec.rb .......... Finished in 0.12633 seconds (files took 2.34 seconds to load) 10 examples, 0 failures
Testing the API Controller
Next, I wanted to check the API for when users authenticate. The 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. This makes subsequent calls stateless. So I'll test my session login controller: rails g rspec:controller api/v1/sessionsI was happy to see it created spec/controllers/api/v1/sessions_controller_spec.rb just like how I have my API!
Here's the first version of my working sessions_controller_spec.rb file:require 'rails_helper'
RSpec.describe Api::V1::SessionsController, :type => :controller do
describe "POST #create" do
before(:each) do
# set this as recommended by Devise so tests pass.
@request.env["devise.mapping"] = Devise.mappings[:user]
@user = FactoryGirl.create :user
end
context "when the credentials are correct" do
before(:each) do
credentials = { :user => {email: @user.email, password: @user.password } }
post :create, credentials
end
it "returns response status 200 given credentials" do
#puts @user
#@user.reload
expect(response.status).to eq(200)
end
it "returns the user authentication token" do
result = JSON.parse(response.body)
expect(result['auth_token']).to eq @user.authentication_token
end
it "returns the username" do
result = JSON.parse(response.body)
expect(result['username']).to eq @user.username
end
it "returns the email" do
result = JSON.parse(response.body)
expect(result['email']).to eq @user.email
end
end
end
end
Running this test:
$ bundle exec rspec spec/controllers/api/v1/sessions_controller_spec.rb .... Finished in 0.10525 seconds (files took 2.18 seconds to load) 4 examples, 0 failuresWow! I feel like a real hipster programmer now! Testing!