I´m trying rails to develop a rest api. I´m using rspec and shoulda-matchers. The problem is that one of my test always fails.
How can I correct my code, so the test passes (GREEN).
user.rb
class User < ActiveRecord::Base
has_many :cards
end
card.rb
class Card < ActiveRecord::Base
belongs_to :user
end
user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
before { #user = FactoryGirl.create(:user) }
subject{ #user }
# Columns
it { should respond_to :name }
# Associations
it { should have_many :cards }
end
Gemfile
source 'https://rubygems.org'
gem 'rails', '4.2.0'
gem 'rails-api'
gem 'spring', :group => :development
gem 'sqlite3'
group :development, :test do
gem 'factory_girl_rails'
gem 'rspec-rails', '~> 3.0'
gem 'shoulda-matchers', '~> 3.0'
end
Terminal
➜ my_api rspec spec/models/user_spec.rb
.F
Failures:
1) User should have many :cards
Failure/Error: it { should have_many :cards }
expected #<User:0x007f897ce0c0d8> to respond to `has_many?`
# ./spec/models/user_spec.rb:12:in `block (2 levels) in <top (required)>'
Finished in 0.04623 seconds (files took 2.45 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:12 # User should have many :cards
You have to setup the association between the models properly in your user and card factories.
factory :card do
# ...
association :user
end
Then, it should work.
See this to know more about associations in factory-girl.
If the above doesn't fix your problem, try doing this way:
# spec/factories/user.rb
Factory.define :user, :class => User do |u|
u.cards { |c| [c.association(:card)] }
end
Related
I need help with my current testing approach. I am currently testing my React-Rails app using Rspec and initially I setup this in my favourite_cocktail controller:
def destroy
#favouritecocktail = FavouriteCocktail.find(params[:id])
#favouritecocktail.delete
end
On testing the DELETE request using the code below:
describe 'DELETE /api/v1/favourite_cocktails/:id' do
let!(:users) { FactoryBot.create(:user) }
let!(:cocktails) { FactoryBot.create(:cocktail) }
let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10, cocktail: cocktails) }
let(:cocktail_id) { favourite_cocktail.first.id }
before do
sign_in users
end
before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }
it 'returns status code 204' do
expect(response).to have_http_status(204)
end
end
it passes, but on my app, the function responsible for deleting the user's favourite cocktail does not work. That is when I click a button to remove a users' favourite cocktail it does not work.
However, if I refactor the destroy action method in the favourite_cocktail controller to this:
def destroy
#favouritecocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id])
#favouritecocktail.delete
end
the function responsible for deleting the user's favourite cocktail works on the application. But when I run the test again:
describe 'DELETE /api/v1/favourite_cocktails/:id' do
let!(:users) { FactoryBot.create(:user) }
let!(:cocktails) { FactoryBot.create(:cocktail) }
let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10, cocktail: cocktails) }
let(:cocktail_id) { favourite_cocktail.first.id }
before do
sign_in users
end
before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }
it 'returns status code 204' do
expect(response).to have_http_status(204)
end
end
it fails and this is the error message I get during the RSpec testing:
Api::V1::FavouriteCocktailsController DELETE /api/v1/favourite_cocktails/:id returns status code 204
Failure/Error: #favouritecocktail.delete
NoMethodError:
undefined method `delete' for nil:NilClass
# ./app/controllers/api/v1/favourite_cocktails_controller.rb:47:in `destroy'
# ./spec/requests/favourite_cocktails_spec.rb:80:in `block (3 levels) in <main>'
# ./spec/rails_helper.rb:112:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:111:in `block (2 levels) in <top (required)>'
Now the preferred approach I want is such that my remove favourite_cocktail should work on the application and the Rspec test should hit the DELETE route such that it passes. I know that there is no record of favourite_cocktails created when using FactoryBot and my concern is how to make FactoryBot create a record to be deleted. Below are codes for the API:
Gemfile
ruby '2.6.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
gem 'devise'
gem 'react-rails'
gem "font-awesome-rails"
gem 'foreman'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# gem 'rspec-rails', '~> 3.8'
gem 'rspec-rails', git: 'https://github.com/rspec/rspec-rails', branch: "4-0-maintenance"
end
group :development do
gem 'guard-rspec', require: false
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'rb-fsevent', '~> 0.10.3'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'web-console', '>= 3.3.0'
end
group :test do
gem 'database_cleaner'
gem 'factory_bot_rails'
gem 'faker'
gem 'shoulda-matchers'
end
routes.rb
Rails.application.routes.draw do
devise_for :users
get 'landing/index'
get '/index', to: 'landing#index', as: 'index'
namespace :api do
namespace :v1 do
resources :cocktails do
put :favourite, on: :member
end
resources :favourite_cocktails, only: %i[create destroy]
resources :favourites_dashboard, only: %i[index]
end
end
root 'landing#app'
match '*path', to: 'landing#app', via: :all
end
Favourite_cocktails controller
module Api
module V1
class FavouriteCocktailsController < ApplicationController
skip_before_action :verify_authenticity_token
def index
#favouritecocktail = current_user.cocktails
if user_signed_in? && #favouritecocktail
render json: {status: 'SUCCESS', message: 'Loading all Favourite Cocktails', data: #favouritecocktail}, status: :ok
else
render json: {}, status: 401
end
end
def create
fav = FavouriteCocktail.new(favourite_params) do |c|
c.user = current_user
end
if fav.save!
render json: { message: 'created' }, status: :created
else
render json: { errors: fav.errors.full_messages },
status: :unprocessable_entity
end
end
def destroy
#favouritecocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id])
#favouritecocktail.delete
end
private
def favourite_params
params.require(:favourite_cocktail).permit(:cocktail_id)
end
end
end
end
Favourite_cocktails factory
FactoryBot.define do
factory :favourite_cocktail do
user
cocktail
end
end
User Factory
FactoryBot.define do
factory :user do
username { Faker::Name.name }
email { Faker::Internet.safe_email }
password { 'foobar' }
password_confirmation { 'foobar' }
end
factory :random_user, class: User do
username { Faker::Name.name }
email { Faker::Internet.safe_email }
password { Faker::Password.password }
password_confirmation { Faker::Password.password_confirmation }
end
end
Cocktails factory
FactoryBot.define do
factory :cocktail do
name { Faker::Restaurant.name }
description { Faker::Lorem.sentence }
ingredients { Faker::Lorem.sentence }
image { Faker::Avatar.image }
end
end
Associations
Favourite_cocktails
class FavouriteCocktail < ApplicationRecord
belongs_to :user
belongs_to :cocktail
validates :user_id, uniqueness: { scope: :cocktail_id }
end
User
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :favourite_cocktails
has_many :favourites, through: :favourite_cocktails, source: :cocktail
validates :username, presence: true, uniqueness: true, allow_blank: false, length: { minimum: 5 }
validates :email, presence: true, length: { minimum: 5 }
end
cocktail
class Cocktail < ApplicationRecord
has_many :favourite_cocktails
has_many :favourited, through: :favourite_cocktails, source: :user
validates :name, presence: true, allow_blank: false, length: { minimum: 5 }
validates :description, presence: true, allow_blank: false, length: { minimum: 10 }
validates :ingredients, presence: true, allow_blank: false, length: { minimum: 10 }
validates :image, presence: true
end
RSpec
Favourite Cocktail Request Spec
require 'rails_helper'
RSpec.describe Api::V1::FavouriteCocktailsController, type: :request do
describe 'POST Favourite Cocktails' do
let!(:users) { FactoryBot.create(:user) }
let!(:cocktails) { FactoryBot.create_list(:cocktail, 10) }
let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10) }
let(:cocktail_id) { cocktails.first.id }
let(:valid_params) do
{ favourite_cocktail: { cocktail_id: cocktails.first.id } }
end
before do
sign_in users
end
context 'when the request is valid' do
before { post '/api/v1/favourite_cocktails', params: valid_params }
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
it 'returns a created status' do
expect(response).to have_http_status(:created)
end
end
end
describe 'GET all favourite cocktails' do
let!(:users) { FactoryBot.create(:user) }
let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10) }
let(:cocktail_id) { cocktails.first.id }
before do
sign_in users
get '/api/v1/favourite_cocktails'
end
it 'returns HTTP status 200' do
expect(response).to have_http_status 200
end
end
describe 'DELETE /api/v1/favourite_cocktails/:id' do
let!(:users) { FactoryBot.create(:user) }
let!(:cocktails) { FactoryBot.create(:cocktail) }
let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10, cocktail: cocktails) }
let(:cocktail_id) { favourite_cocktail.first.id }
before do
sign_in users
end
before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }
# thing = create(:thing)
# delete '/things', :thing => { :id => thing.id'}
it 'returns status code 204' do
expect(response).to have_http_status(204)
end
end
end
If there are other things you'd like to see to get this working please let me know. Thanks for your help.
It looks to me like your RSpec test setup isn't recreating a valid "happy path" scenario, as the cocktail you are trying to delete doesn't actually belong to the user you signed in as.
Here's a slight refactoring that I think should help fix the test:
describe 'DELETE /api/v1/favourite_cocktails/:id' do
let!(:user) { FactoryBot.create(:user) }
let!(:cocktail) { FactoryBot.create(:cocktail) }
# Adding `user: user` is the important bit here according to your factory
let!(:favourite_cocktail) { FactoryBot.create(:favourite_cocktail, user: user cocktail: cocktail) }
let(:cocktail_id) { favourite_cocktail.id }
before do
sign_in users
end
before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }
it 'returns status code 204' do
expect(response).to have_http_status(204)
end
end
And here's a change I'd make to the delete implementation so that trying to delete a cocktail you don't have as a favorite doesn't throw an exception and 500:
def destroy
#favouritecocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id])
#favouritecocktail.delete if #favouritecocktail
end
Of course, this will return success even if it fails to delete, but you could easily send a 400-level response if that is preferable to your application.
Simplecov detected that I was missing some tests on my lib/api_verson.rb class:
class ApiVersion
def initialize(version)
#version = version
end
def matches?(request)
versioned_accept_header?(request) || version_one?(request)
end
private
def versioned_accept_header?(request)
accept = request.headers['Accept']
accept && accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}-v#{#version}\+json/]
end
def unversioned_accept_header?(request)
accept = request.headers['Accept']
accept.blank? || accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}/].nil?
end
def version_one?(request)
#version == Rails.application.secrets.my_app_default_api_version && unversioned_accept_header?(request)
end
end
This class is used by the routes file to help setup api versions:
namespace :api, path: "", defaults: {format: :json} do
scope module: :v1, constraints: ApiVersion.new(1) do
get '/alive', to: 'api#alive'
end
scope module: :v2, constraints: ApiVersion.new(2) do
get '/alive', to: 'api#alive'
end
end
This setup was ported from versioning_your_ap_is.
I am trying to test the methods here that simplecov is reporting as failures:
require 'spec_helper'
describe ApiVersion do
before(:each) do
#apiversion = ApiVersion.new(1)
#request = ActionController::TestRequest.new(host: 'localhost')
#request.headers["Accept"] = "application/vnd.#{Rails.application.secrets.my_app_accept_header}-#{Rails.application.secrets.my_app_default_api_version}+json"
end
describe 'Method #versioned_accept_header =>' do
it 'Should return the correct accept header version' do
binding.pry
end
end
end
I am trying to build this first test to attempt??? to #apiversion.send(:unversioned_accept_header, #request) and I am getting the error:
#apiversion.send(:unversioned_accept_header, #request)
NoMethodError: undefined method `unversioned_accept_header' for #<ApiVersion:0x007fae009bdad8 #version=1>
from (pry):1:in `block (3 levels) in <top (required)>'
Basically the following methods are flagged: "matches?, versioned_accept_header?, unversioned_accept_header?, and version_one?"
I am not the rockstar at rspec and could use some pointers here. Thank you for your help.
Btw, this is a rails 4 application running:
group :development, :test do
gem 'pry'
gem 'pry-doc'
gem 'pry-debugger'
gem 'pry-rails'
gem 'pry-plus'
gem 'pry-rescue'
gem 'pry-stack_explorer'
gem 'pry-clipboard'
gem 'pry-nav'
gem 'rspec-rails'
gem 'factory_girl_rails'
gem 'faker'
gem 'seedbank'
gem 'capybara'
end
group :test do
gem 'simplecov', '~> 0.7.1'
gem 'shoulda-matchers'
gem 'spork-rails'
gem 'database_cleaner'
gem 'email_spec'
gem 'timecop'
gem 'json_spec'
end
You run this code
#apiversion.send(:unversioned_accept_header, #request)
but you have method:
def unversioned_accept_header?(request)
accept = request.headers['Accept']
accept.blank? || accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}/].nil?
end
try change
#apiversion.send(:unversioned_accept_header, #request) -> #apiversion.send(:unversioned_accept_header?, #request)
or change method name
def unversioned_accept_header? -> def unversioned_accept_header
I recently upgraded to Rails 4 and everything works fine except for my Rspec tests.
require 'spec_helper'
describe Invoice do
before :each do
#user = FactoryGirl.create(:activated_user)
person = FactoryGirl.create(:person, :user => #user, :company => nil)
#project = FactoryGirl.create(:project, :user => #user, :person_ids => [person.id], :invoice_recipient_id => person.id)
end
it "has a valid factory" do
expect(FactoryGirl.build(:invoice, :project => #project, :user => #user)).to be_valid
end
it "is invalid without a number" do
expect(FactoryGirl.build(:invoice, :project => #project, :user => #user, :number => nil)).to have(1).errors_on(:number)
end
end
When running these tests I get this error:
Failure/Error: expect(FactoryGirl.build(:invoice, :project => #project, :user => #user, :number => nil)).to have(1).errors_on(:number)
NoMethodError:
undefined method `have' for #<RSpec::ExampleGroups::Invoice_2:0x009ge29360d910>
# ./spec/models/invoice_spec.rb:16:in `block (2 levels) in <top (required)>'
Can anybody tell me what I am missing here?
I googled it already but nothing came up. The have method is actually fairly standard in Rspec and I can't see why it shouldn't work.
Thanks for any pointers.
The have family of matchers was deprecated in RSpec 2.99 and has been moved to a separate rspec-collection_matchers gem as of RSpec 3.0. This is discussed in http://myronmars.to/n/dev-blog/2013/11/rspec-2-99-and-3-0-betas-have-been-released, which also gives the suggested approach to migrating to 3.0. Specifically, it recommends installing/using RSpec 2.99 in order to see the deprecation messages associated with items that were removed/moved in 3.0.
In the latest versions of rspec "have" being deprecated, but you still can use it via rspec-collection_matchers gem.
# Gemfile
...
gem 'rspec-collection_matchers', group: :test
...
# spec/spec_helper.rb
...
require 'rspec/collection_matchers'
....
OK, got it.
I had the wrong version number in my Gemfile.
Before:
gem 'rspec-rails', '~> 3.0.0.beta'
After:
gem 'rspec-rails'
I am trying to create my first controller test using FactoryGirl for my Rails application, but I keep retrieving the following error:
uninitialized constant FactoryGirl
My Factories.rb file looks like this:
FactoryGirl.define do
factory :offer, class: "Offer" do |f|
f.title "Some title"
f.description "SomeDescription"
end
end
And my controller test looks like this:
require 'spec_helper'
describe OffersController do
def valid_session
{}
end
describe "GET index" do
before {
#offer = FactoryGirl.create(:offer)
}
it "assigns all offers as #offers" do
get :index, {}, valid_session
assigns(:offers).should eq([#offer])
end
end
end
My Gemfile looks like this:
group :development, :test do
gem 'sqlite3'
gem 'rspec-rails'
gem 'capybara', '1.1.2'
gem 'factory_girl_rails', '>= 4.1.0', :require => false
end
What might I be missing since FactoryGirl isn't present?
You propably forgotten to require factory_girl in spec_helper.rb.
I'm attempting to follow railstutorial.org, and am currently on Chapter 7, where you start using factories: http://railstutorial.org/chapters/modeling-and-viewing-users-two#sec:tests_with_factories
I'm using Rails 3.0.1 and ruby-1.9.2-p0
I can't for the life of me get my rspec tests to pass though, the error i get is
Failures:
1) UsersController GET 'show' should be successful
Failure/Error: #user = Factory(:user)
undefined method `Factory' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_1:0x00000101cc5608>
# ./spec/controllers/users_controller_spec.rb:9:in `block (3 levels) in <top (required)>'
my factories.rb looks like this:
# By using the symbol ':user', we get Factory Girl to simulate the User model.
Factory.define :user do |user|
user.name "Michael Hartl"
user.email "mhartl#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end
and this is my users_controller_spec.rb file:
require 'spec_helper'
describe UsersController do
render_views
describe "GET 'show'" do
before(:each) do
#user = Factory(:user)
end
it "should be successful" do
get :show, :id => #user
response.should be_success
end
here is my Gemfile, if it helps:
source 'http://rubygems.org'
gem 'rails', '3.0.1'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3-ruby', :require => 'sqlite3'
gem 'gravatar_image_tag'
group :development do
gem 'rspec-rails'
gem 'annotate-models'
end
group :test do
gem 'rspec'
gem 'webrat'
gem 'spork'
gem 'factory_girl_rails'
end
As per the latest version of Factory Girl (currently v4.0.0)
rewrite factories.rb
FactoryGirl.define do
factory :user do
name "Michael Hartl"
email "mhartl#example.com"
password "foobar"
password_confirmation "foobar"
end
end
then call it from your users controller specs as:
FactoryGirl.create(:user)
I got this exact same error message. I just restarted my Spork server and Autotest and everything went green for me.
Maybe you should try the new syntax (see github readme of factory girl)
FactoryGirl.define :user do |user|
user.name "Michael Hartl"
user.email "mhartl#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end
In your spec use
#user = FactoryGirl(:user)
instead of
#user = Factory(:user)
I had this problem, but it was because I had placed the factory girl gem under the development section instead of the test section of the Gemfile. Once under the test section, it worked. One difference I note between my entry and yours is that mine specifies 1.0:
group :test do
gem 'rspec-rails', '2.6.1'
gem 'webrat', '0.7.1'
gem 'factory_girl_rails', '1.0'
end
For me I had to add require 'factory_girl' to test_helper.rb
My solution: I've accidentally included it in the :development block, and simply had to move it to the :test block
(I've listed it here, because it might help someone who doesn't follow the tutorial correctly)
I have done so,
add require 'factory_girl' to test_helper.rb and
#user = FactoryGirl.create(:user)
For those finding this page now: note where you once used "FactoryGirl" you must now use "FactoryBot" in your tests. From the thoughtbot announcement page:
"We’re renaming factory_girl to factory_bot (and factory_girl_rails to
factory_bot_rails). All the same functionality of factory_girl, now
under a different name."
More details here:
https://robots.thoughtbot.com/factory_bot
I was determined to use the newest version of Factory Girl, so I tried to adapt the code. Didn't work for me, so I used
gem 'factory_girl_rails', '1.0'
in the Gemfile to lock the version at 1.0
bundle update
restart spork and autotest and it worked.