Background: I've got an after_action callback in my controller, which takes the string address, processes it and stores longitude and latitude in corresponding fields. I want to test this.
This SO question, as well as this article only consider update methods, but at least, they are quite clear, because I've already got an object to work with.
So my question is - how to find this newly created record? This SO question led me to this code:
require 'rails_helper'
RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do
context "POST methods" do
describe "#edit and #create" do
it "encodes and stores lang/lot correctly" do
post :create, general_setting: FactoryGirl.attributes_for(:general_setting)
expect(assigns(:general_setting).long).to eq(37.568021)
# expect(general_setting.long).to eq(37.568021)
# expect(general_setting.lat).to eq(55.805553)
end
end
end
end
But using the code in the answer, I get this error:
Failure/Error: expect(assigns(:general_setting).long).to eq(37.568021)
NoMethodError:
undefined method `long' for nil:NilClass
Update #1:
This is my new controller spec code:
RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do
context 'POST methods' do
before(:each) do
allow(subject).to receive(:set_long_lat)
end
describe 'post create' do
before(:each) do
post :create, params: { general_setting: FactoryGirl.attributes_for(:general_setting) }
end
it "saves the record with valid attributes" do
expect{subject}.to change{GeneralSetting.count}.by(1)
end
it 'calls :set_long_lat' do
expect(subject).to have_received(:set_long_lat)
end
end
end
describe '#set_long_lat' do
# spec for method
end
end
Update #2:
Here is my controller code:
class Admin::Settings::GeneralSettingsController < AdminController
include CrudConcern
before_action :find_general_setting, only: [:edit, :destroy, :update, :set_long_lat]
after_action :set_long_lat
def index
#general_settings = GeneralSetting.all
end
def new
#general_setting = GeneralSetting.new
# Билдим для того, что бы было видно сразу одно поле и пользователь не должен
# кликать на "добавить телефон"
#general_setting.phones.build
#general_setting.opening_hours.build
end
def edit
# Тоже самое, что и с нью - если телефонов нет вообще, то показываем одно пустое поле
if #general_setting.phones.blank?
#general_setting.phones.build
end
if #general_setting.opening_hours.blank?
#general_setting.opening_hours.build
end
end
def create
#general_setting = GeneralSetting.new(general_setting_params)
create_helper(#general_setting, "edit_admin_settings_general_setting_path")
end
def destroy
destroy_helper(#general_setting, "admin_settings_general_settings_path")
end
def update
# debug
# #general_setting.update(language: create_hash(params[:general_setting][:language]))
#general_setting.language = create_hash(params[:general_setting][:language])
update_helper(#general_setting, "edit_admin_settings_general_setting_path", general_setting_params)
end
private
def set_long_lat
geocoder = Geocoder.new
data = geocoder.encode!(#general_setting.address)
#general_setting.update!(long: data[0], lat: data[1])
end
def find_general_setting
#general_setting = GeneralSetting.find(params[:id])
end
def general_setting_params
params.require(:general_setting).permit(GeneralSetting.attribute_names.map(&:to_sym).push(
phones_attributes: [:id, :value, :_destroy, :general_setting_id ]).push(
opening_hours_attributes: [:id, :title, :value, :_destroy, :deneral_setting_id]) )
end
def create_hash(params)
language_hash = Hash.new
params.each do |param|
language_hash[param.to_sym] = param.to_sym
end
return language_hash
end
end
(If it helps - I've got a lot of similar crud-actions, that is why I've put them all in a concern controller)
module CrudConcern
extend ActiveSupport::Concern
include Language
included do
helper_method :create_helper, :update_helper, :destroy_helper, :get_locales
end
def get_locales
#remaining_locales = Language.get_remaining_locales
end
def create_helper(object, path)
if object.save!
respond_to do |format|
format.html {
redirect_to send(path, object)
flash[:primary] = "Well done!"
}
end
else
render :new
flash[:danger] = "Something not quite right"
end
#remaining_locales = Language.get_remaining_locales
end
def update_helper(object, path, params)
if object.update!(params)
respond_to do |format|
format.html {
redirect_to send(path, object)
flash[:primary] = "Well done!"
}
end
else
render :edit
flash[:danger] = "Something's not quite right"
end
end
def destroy_helper(object, path)
if object.destroy
respond_to do |format|
format.html {
redirect_to send(path)
flash[:primary] = "Well done"
}
end
else
render :index
flash[:danger] = "Something's not quite right"
end
end
end
Update #3
It's not the ideal solution, but, somehow, controller tests just won't work. I've moved my callback into the model and updated my general_setting_spec test.
class GeneralSetting < ApplicationRecord
after_save :set_long_lat
validates :url, presence: true
private
def set_long_lat
geocoder = Geocoder.new
data = geocoder.encode(self.address)
self.update_column(:long, data[0])
self.update_column(:lat, data[1])
end
end
My tests now:
RSpec.describe GeneralSetting, type: :model do
let (:regular) { FactoryGirl.build(:general_setting) }
describe "checking other validations" do
it "is invalid with no url" do
expect{
invalid.save
}.not_to change(GeneralSetting, :count)
end
it 'autofills the longitude' do
expect{ regular.save }.to change{ regular.long }.from(nil).to(37.568021)
end
it 'autofills the latitude' do
expect{ regular.save }.to change{ regular.lat }.from(nil).to(55.805078)
end
end
end
I would test expectation that controller calls method specified in after_action and make a separate test for that method.
Something like:
context 'POST methods' do
before(:each) do
allow(subject).to receive(:method_from_callback)
end
describe 'post create' do
before(:each) do
post :create, params: { general_setting: attributes_for(:general_setting) }
end
it 'calls :method_from_callback' do
expect(subject).to have_received(:method_from_callback)
end
end
end
describe '#method_from_callback' do
# spec for method
end
Be sure to use your method name instead of :method_from_callback pay attention that I've used rspec 3.5 syntax (wrapped request request parameters into params).
Related
I have a class, that in one situation should call :my_method, but in another situation must not call method :my_method. I would like to test both cases. Also, I would like the test to document the cases when :my_method should not be called.
Using any_instance is generally discouraged, so I would be happy to learn a nice way to replace it.
This code snippet is a reduced example on what I kind of test I would like to write.
class TestSubject
def call
call_me
end
def call_me; end
def never_mind; end
end
require 'rspec'
spec = RSpec.describe 'TestSubject' do
describe '#call' do
it 'calls #call_me' do
expect_any_instance_of(TestSubject).to receive(:call_me)
TestSubject.new.call
end
it 'does not call #never_mind' do
expect_any_instance_of(TestSubject).not_to receive(:never_mind)
TestSubject.new.call
end
end
end
spec.run # => true
It works, but uses expect_any_instance_of method, which is not recommended.
How to replace it?
I'll do somehting like that
describe TestSubject do
describe '#call' do
it 'does not call #something' do
subject = TestSubject.new
allow(subject).to receive(:something)
subject.call
expect(subject).not_to have_received(:something)
end
end
end
Hope this helped !
This is how I normally unit-test. I updated the code to support other possible questions you (or other readers) may have in the future.
class TestSubject
def call
some_call_me_value = call_me
call_you(some_call_me_value)
end
def call_me; end
def call_you(x); end
def never_mind; end
class << self
def some_class_method_a; end
def some_class_method_b(x, y); end
end
end
require 'rspec'
spec = RSpec.describe TestSubject do
context 'instance methods' do
let(:test_subject) { TestSubject.new }
describe '#call' do
let(:args) { nil }
let(:mocked_call_me_return_value) { 'somecallmevalue' }
subject { test_subject.call(*args) }
before do
allow(test_subject).to receive(:call_me) do
mocked_call_me_return_value
end
end
it 'calls #call_me' do
expect(test_subject).to receive(:call_me).once
subject
end
it 'calls #call_you with call_me value as the argument' do
expect(test_subject).to receive(:call_you).once.with(mocked_call_me_return_value)
subject
end
it 'does not call #never_mind' do
expect(test_subject).to_not receive(:never_mind)
subject
end
it 'calls in order' do
expect(test_subject).to receive(:call_me).once.ordered
expect(test_subject).to receive(:call_you).once.ordered
subject
end
end
describe '#call_me' do
let(:args) { nil }
subject { test_subject.call_me(*args) }
# it ...
end
describe '#call_you' do
let(:args) { nil }
subject { test_subject.call_you(*args) }
shared_examples_for 'shared #call_you behaviours' do
it 'calls your phone number'
it 'creates a Conversation record'
end
# just an example of argument-dependent behaviour spec
context 'when argument is true' do
let(:args) { [true] }
it 'does something magical'
it_behaves_like 'shared #call_you behaviours'
end
# just an example of argument-dependent behaviour spec
context 'when argument is false' do
let(:args) { [false] }
it 'does something explosive'
it_behaves_like 'shared #call_you behaviours'
end
end
end
context 'class methods' do
let(:args) { nil }
describe '#some_class_method_a' do
let(:args) { nil }
subject { TestSubject.some_class_method_a(*args) }
# it ...
end
describe '#some_class_method_b' do
let(:args) { [1, 2] }
subject { TestSubject.some_class_method_b(*args) }
# it ...
end
end
end
spec.run # => true
Do not test if some method was called or wasn't.
This will tight your tests to the implementation details and will force you to change tests every time you refactor(change implementation details without changing the behaviour) your class under test.
Instead test against return value or changed application state.
It is difficult come up with the example, you didn't provide enough context about the class under the test.
class CreateEntity
def initialize(name)
#name = name
end
def call
if company_name?(#name)
create_company
else
create_person
end
end
def create_person
Person.create!(:name => #name)
end
def create_company
Company.create!(:name => #name)
end
end
# tests
RSpec.describe CreateEntity do
let(:create) { CreateEntity.new(name).call }
describe '#call' do
context 'when person name is given' do
let(:name) { 'Firstname Lastname' }
it 'creates a person' do
expect { create }.to change { Person.count }.by(1)
end
it 'do not create a company' do
expect { create }.not_to change { Company.count }
end
end
context 'when company name is given' do
let(:name) { 'Name & Sons Ltd' }
it 'creates a company' do
expect { create }.to change { Company.count }.by(1)
end
it 'do not create a person' do
expect { create }.not_to change { Person.count }
end
end
end
end
With tests above I would be able to change how CreateEntity.call method implemented without changing tests as far as behaviour remain same.
I have this test:
it "redirects him to vice" do
get :show
uri_request_1 = Addressable::URI.parse(response.redirect_url)
redirects_to_vice_1 = uri_request_1.host == "vice.com"
get :show, relative_path: "/Fun/Facts.html"
uri_request_2 = Addressable::URI.parse(response.redirect_url)
redirects_to_vice_2 = uri_request_1.host == "vice.com"
assert redirects_to_vice_1 && redirects_to_vice_2
end
It has a lot of repetition. How can I shorten this test?
I would think about adding a helper method:
def assert_redirection_to(host)
assert Addressable::URI.parse(response.redirect_url) == host
end
With such a method you could change your tests to:
describe 'without parameters' do
it 'redirects to vice.com' do
get :show
assert_redirection_to 'vice.com'
end
end
describe 'with parameters' do
it 'redirects to vice.com'
get :show, relative_path: '/Fun/Facts.html'
assert_redirection_to 'vice.com'
end
end
[[:show], [:show, relative_path: "/Fun/Facts.html"]].each do
|args|
get(*args)
assert Addressable::URI.parse(response.redirect_url).host == "vice.com"
end
I'm using a custom class to make AR instances from Feedjirra. I can't get the children instances to relate to their parent objects.
Show has_many :episodes -
Episode belongs_to :show -
show_id is always nil.
RSpec logs #show.id and #episode.show_id as equal to one another. However when I run episode = Episode.first after running an import in development, the episode has its show_id set to nil.
#show = Show.new
#show.name = #feed.title
#show.description = #feed.description
...
if #show.save
puts "#show.id: #{#show.id}"
end
#episodes = []
#feed.entries.each do |item|
#episodes.push(item)
end
#episodes.each do |item|
#episode = #show.episodes.new
#episode.name = item.title
#episode.description = item.summary
...
if #episode.save
puts "#episode.show_id: #{#episode.show_id}"
end
end
I tried using #episode = #show.episodes.create, as well as #episode = Episode.new with #episode.show_id = #show.id. They all log matching IDs but show_id is still nil on the instances. Every other column is filled in correctly.
I thought the issue may have had to do with using add_foreign_key:
class AddShowToEpisodes < ActiveRecord::Migration
def change
add_reference :episodes, :show, index: true
add_foreign_key :episodes, :shows, column: :show_id
end
end
So I removed that and used the standard foreign_key: true but it had no effect.
class RemoveShowFromEpisodes < ActiveRecord::Migration
def change
remove_column :episodes, :show_id
end
end
class AddShowBackToEpisodes < ActiveRecord::Migration
def change
add_reference :episodes, :show, index: true, foreign_key: true
end
end
Here's the full code in case it helps.
importers_controller.rb:
class Admin::ImportersController < Admin::ApplicationController
before_action :set_importer, only: [:show, :edit, :update, :destroy]
def index
#importers = policy_scope(Importer)
end
def show
end
def new
#importer = Importer.new
authorize #importer
end
def create
#importer = Importer.new(importer_params)
authorize #importer
if #importer.save
require "subscription_importer"
SubscriptionImporter.new(#importer)
flash[:notice] = "Importer added."
redirect_to admin_importers_path
else
flash[:error] = "Importer not added."
render "new"
end
end
def edit
end
def update
end
def destroy
end
private
def set_importer
#importer = Importer.find(params[:id])
authorize #importer
end
def importer_params
params.require(:importer).permit(:name, :url, :source)
end
end
subscription_importer.rb:
class SubscriptionImporter
def initialize(importer)
#importer = importer
#feed = Feedjira::Feed.fetch_and_parse #importer.url
if #importer.source === "iTunes"
itunes_parser(#importer)
end
end
def itunes_parser(importer)
#importer = importer
# Parser
#feed = Feedjira::Feed.fetch_and_parse #importer.url
# Show
#show = Show.new
#show.name = #feed.title
#show.description = #feed.description
#show.logo = #feed.itunes_image
#show.explicit = explicit_check(#feed.itunes_explicit)
#show.genre = #feed.itunes_categories
#show.tags = #feed.itunes_keywords
#show.url = #feed.url
#show.language = #feed.language
if #show.save
puts "Show import succeeded"
puts "#show.id: #{#show.id}"
else
puts "Show import failed"
end
# Episodes
#episodes = []
#feed.entries.each do |item|
#episodes.push(item)
end
#episodes.each do |item|
#episode = #show.episodes.new
#episode.name = item.title
#episode.description = item.summary
#episode.release = item.published
#episode.image = item.itunes_image
#episode.explicit = explicit_check(item.itunes_explicit)
#episode.tags = item.itunes_keywords
#episode.url = item.enclosure_url
#episode.duration = item.itunes_duration
if #episode.save
puts "Episode import succeeded"
puts "#episode.show_id: #{#episode.show_id}"
else
puts "Episode import failed"
end
end
end
def explicit_check(string)
if string == "yes" || "Yes"
true
else
false
end
end
end
create_importer_spec.rb:
require "rails_helper"
RSpec.feature "Admins can create importers" do
let(:user) { FactoryGirl.create(:user, :admin) }
context "admins" do
before do
login_as(user)
visit "/"
click_link "Admin"
click_link "Importers"
click_link "New Importer"
end
scenario "with valid credentials" do
fill_in "Name", with: "The Stack Exchange Podcast"
fill_in "Url", with: "https://blog.stackoverflow.com/feed/podcast/" # Needs stubbing
select "iTunes", from: "Source"
click_button "Create Importer"
expect(page).to have_content "Importer added"
expect(page).to have_content "The Stack Exchange Podcast"
end
scenario "with invalid credentials" do
fill_in "Name", with: ""
fill_in "Url", with: ""
click_button "Create Importer"
expect(page).to have_content "Importer not added"
end
end
end
I think the episodes functionality in your SubscriptionImporter class is causing the problem...
#episodes = []
#feed.entries.each do |item|
#episodes.push(item) #-> each "#episodes" is a FeedJirra object
end
#episodes.each do |episode|
#-> you're now creating an episode in the same call as show, which will either mean that show is not persisted or perhaps some other error
end
I would personally limit the SubscriptionImporter functionality to only return data. You should be parsing that data through the respective models:
#app/controllers/admin/importers_controller.rb
class Admin::ImportersController < Admin::ApplicationController
def create
#import = Importer.new import_params
if #import.save
#import.parse_show if #import.itunes?
end
end
private
def import_params
params.require(:importer).permit(:name, :url, :source)
end
end
#app/models/importer.rb
class Importer < ActiveRecord::Base
def feed
return false unless itunes?
origin = Feedjirra::Feed.fetch_and_parse(self.url)
return {
name: origin.title,
description: origin.description,
logo: origin.itunes_image,
explicit: explicit_check(origin.itunes_explicit),
genre: origin.itunes_categories,
tags: origin.itunes_keywords,
url: origin.url,
language: origin.language,
entries: origin.entries
}
end
def parse_show
Show.create(feed)
end
def itunes?
self.source == "iTunes" #-> true/false
end
private
def explicit_check
string == "yes" || "Yes" #-> true/false
end
end
#app/models/show.rb
class Show < ActiveRecord::Base
has_many :episodes
attr_accessor :entries
after_create :create_episodes #-> might not persist entries
def create_episodes
if self.entries.any?
self.entries.each do |item|
self.episodes.create({
name: item.title
description: item.summary,
release: item.published,
image: item.itunes_image,
explicit: explicit_check?(item.itunes_explicit),
tags: item.itunes_keywords,
url: item.enclosure_url,
duration: item.itunes_duration
})
end
end
end
private
def explicit_check?
string == "yes" || "Yes"
end
end
The above will allow you to create an #importer, pull the feed from it, and populate Show & Episode models with the returned data.
Whilst this should resolve your issue, you need to consider OOP -- making each element an object.
Update
If you wanted to objectify this even more, there is a simple pattern to adopt:
Importer is all you need to save -- everything else should happen around this
Show + Episode could be the same class / table for all I know
With this in mind, you could do the following:
#app/controllers/admin/importers_controller.rb
class Admin::ImportersController < Admin::ApplicationController
def create
#import = Importer.new import_params
#import.save
end
private
def import_params
params.require(:importer).permit(:name, :url, :source)
end
end
#app/services/feed.rb
class Feed
attr_reader :params, :show, :episode, :origin
def initialize(params)
#params = params
end
def origin
#origin = Feedjirra::Feed.fetch_and_parse params[:source]
end
def show
#show = ShowHelper.new #origin
end
def episodes
#show.episodes
end
end
#app/services/show_helper.rb
class ShowHelper
attr_reader :origin
def initialize(origin)
#origin = origin
end
def name
#origin.title
end
def description
#origin.summary || #origin.description
end
def logo
#origin.itunes_image
end
def explicit
%r{^yes$} =~ #origin.itunes_explicit
end
def genre
#origin.itunes_categories
end
def tags
#origin.itunes_keywords
end
def url
#origin.url
end
def language
#origin.language
end
def episodes
#origin.entries
end
end
#app/models/importer.rb
class Importer < ActiveRecord::Base
after_create :parse_show, if: "itunes?"
validates :source, :url, :name, presence: true
def itunes?
source == "iTunes"
end
def feed
#feed = Feed.new(self)
end
private
def parse_show
#show = Show.new(feed.show) if feed && feed.show
if #show.save && #show.entries.any?
#show.entries.each do |entry|
#show.episodes.create ShowHelper.new(entry)
end
end
end
end
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, and right now I am stuck on the case where the private method contains a private method...
def version_one?(request)
#version == Rails.application.secrets.my_app_default_api_version && unversioned_accept_header?(request)
end
This is my current spec:
require 'spec_helper'
describe ApiVersion do
before(:each) do
#apiversion = ApiVersion.new(1)
#current_api_version = Rails.application.secrets.my_app_default_api_version
#request = ActionController::TestRequest.new(host: 'localhost')
#request.headers["Accept"] = "application/vnd.#{Rails.application.secrets.my_app_accept_header}-v#{#current_api_version}+json"
end
describe "Method #versioned_accept_header? =>" do
it "Should return false if the header accept variable contains application/vnd.#{Rails.application.secrets.my_app_accept_header}-v#{#current_api_version}+json" do
expect(#apiversion.send(:unversioned_accept_header?, #request)).to eq(false)
end
it "Should return true if no version is included with the header" do
request = #request
request.headers["Accept"] = nil
expect(#apiversion.send(:unversioned_accept_header?, request)).to eq(true)
end
end
describe "Method #version_one? =>" do
it "test me" do
# #apiversion.send(:unversioned_accept_header?, #request)
binding.pry
# expect(#apiversion.send(:version_one?, #request)).to eq(false)
end
end
end
How do I stub the nested private method to test the version_one private method?
Here is how I ended up with my final tests and coverage at 100%.
The lib/api_version.rb file:
class ApiVersion
def initialize(version)
#version = version
end
def matches?(request)
versioned_accept_header?(request) || version_default?(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_default?(request)
#version == Rails.application.secrets.my_app_default_api_version && unversioned_accept_header?(request)
end
end
Followed up by all of the rspec tests:
require 'spec_helper'
describe ApiVersion do
before(:each) do
#apiversion = ApiVersion.new(1)
#current_api_version = Rails.application.secrets.my_app_default_api_version
#request = ActionController::TestRequest.new(host: 'localhost')
end
describe "Method #matches? =>" do
it "Should return false when the header is not versioned and it's not the header default." do
#apiversion.stub(:versioned_accept_header?).and_return(false)
#apiversion.stub(:version_default?).and_return(false)
expect(#apiversion.matches?(#request)).to eq(false)
end
it "Should return true when the proper header has been supplied but is unversioned." do
#apiversion.stub(:versioned_accept_header?).and_return(true)
#apiversion.stub(:version_default?).and_return(false)
expect(#apiversion.matches?(#request)).to eq(true)
end
it "Should return true when the proper header has been supplied and is versioned." do
#apiversion.stub(:versioned_accept_header?).and_return(true)
#apiversion.stub(:version_default?).and_return(true)
expect(#apiversion.matches?(#request)).to eq(true)
end
end
describe "Private method #unversioned_accept_header? =>" do
it "Should return false if the header accept variable contains version 'application/vnd.#{Rails.application.secrets.my_app_accept_header}' in it." do
#request.headers["Accept"] = "application/vnd.#{Rails.application.secrets.my_app_accept_header}"
expect(#apiversion.send(:unversioned_accept_header?, #request)).to eq(false)
end
it "Should return true if no version is included with the header." do
#request.headers["Accept"] = nil
expect(#apiversion.send(:unversioned_accept_header?, #request)).to eq(true)
end
end
describe "Private method #versioned_accept_header? =>" do
it "Should return true if the header accept variable contains version 'application/vnd.#{Rails.application.secrets.my_app_accept_header}.v#{Rails.application.secrets.my_app_default_api_version}+json' in it." do
#header = "application/vnd.#{Rails.application.secrets.my_app_accept_header}-v#{Rails.application.secrets.my_app_default_api_version}+json"
#request.headers["Accept"] = #header
expect(#apiversion.send(:versioned_accept_header?, #request)).to eq(#header)
end
end
describe "Private method #version_default? =>" do
it "Should return false when the proper header version is supplied." do
#apiversion.stub(:unversioned_accept_header?).and_return(false)
expect(#apiversion.send(:version_default?, #request)).to eq(false)
end
it "Should return true when no header is supplied, or a header different than 'application/vnd.#{Rails.application.secrets.my_app_accept_header}' is supplied." do
#apiversion.stub(:unversioned_accept_header?).and_return(true)
expect(#apiversion.send(:version_default?, #request)).to eq(true)
end
end
end
I have a Transaction model, in which I have the following scope :
scope :ownership, -> { where property: true }
I made some tests of the controller (thanks to M. Hartl). There they are :
require 'spec_helper'
describe TransactionsController do
let(:user) { FactoryGirl.create(:user) }
let(:product) { FactoryGirl.create(:givable_product) }
before { be_signed_in_as user }
describe "Ownerships" do
describe "creating an ownership with Ajax" do
it "should increment the Ownership count" do
expect do
xhr :post, :create, transaction: { property: true, user_id: user.id, product_id: product.id }
end.to change(Transaction.ownership, :count).by(1)
end
it "should respond with success" do
xhr :post, :create, transaction: { property: true, user_id: user.id, product_id: product.id }
expect(response).to be_success
end
end
describe "destroying an ownership with Ajax" do
let(:ownership) { user.transactions.ownership.create(product_id: product.id, user_id: user.id) }
it "should decrement the Ownership count" do
expect do
xhr :delete, :destroy, id: ownership.id
end.to change(Transaction.ownership, :count).by(-1)
end
it "should respond with success" do
xhr :delete, :destroy, id: ownership.id
expect(response).to be_success
end
end
end
end
And there is the destroy method of my Transaction controller :
def destroy
#transaction = Transaction.find(params[:id])
#property = #transaction.property
#product = #transaction.product
#transaction.destroy
respond_to do |format|
format.html { redirect_to #product }
format.js
end
end
But when I run the tests, one of them fails, and I don't understand how or why :
1) TransactionsController Ownerships destroying an ownership with Ajax should decrement the Ownership count
Failure/Error: expect do
count should have been changed by -1, but was changed by 0
# ./spec/controllers/transactions_controller_spec.rb:31:in `block (4 levels) in <top (required)>'
Can you help me about it ?
You can use 'let!'.
About 'let' and 'let!': https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
From the RSpec documentation there's a difference between let and let! (see here);
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first
time the method it defines is invoked. You can use let! to force the
method's invocation before each example.
In your destroy method use let!(:ownership) so that the ownership object is not cached after it is destroyed.