# Gemfile
group :test do
#...
gem 'selleo-controller_tests'
end
then bundle
Notice For rspec-rails version 2.x put this config to spec/spec_helper.rb, for 3.x to: spec/rails_helper.rb
#...
require 'selleo_controller_tests'
#...
RSpec.configure do |config|
#...
config.include(Selleo::XhrPersistence)
config.with_options(type: :controller) do |config|
config.extend Selleo::ControllerMacros
end
#...
Notice: All shared examples suppose, you have defined the call_request
block, in which you call the action.
For instance:
describe '#show' do
let(:call_request) { get :show, id: 123 }
end
describe '#create' do
it_behaves_like 'an action redirecting to', -> { specific_path }
end
describe '#create' do
it_behaves_like 'an action redirecting to back'
end
describe '#show' do
it_behaves_like 'an action rendering view'
end
It checks if the action rendered view which is specified in the describe block.
describe '#show' do
context 'inside a nested block' do
it_behaves_like 'an action rendering view'
end
end
describe '#show' do
it_behaves_like 'an action rendering view' do
let(:view) { 'custom_view' }
end
end
By overriding the view variable, you can specify a view, which is rendered in the action.
These shared examples allows you to check, if the request has created a new object. It requires to set the 'attributes' let block, with new attributes for the object.
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
it_behaves_like 'an action creating object', [:name, :age]
end
A class of an object is evaluated from the described class name, e.g.
if the described class is: UsersController
, the created object has class User
.
If the object's class is different, you can override it:
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
it_behaves_like 'an action creating object', [:name, :age] do
let(:model_class) { Admin }
end
end
Also, sometimes it's necessary to specify the new attributes:
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
it_behaves_like 'an action creating object', [:name, :age] do
let(:new_attributes) { {admin?: true} }
end
end
When you find that the object should be created, but you don't want to specify the fields you can easily omit them:
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
it_behaves_like 'an action creating object'
end
Sometimes, when for example you have roles in your system, you can specify, that the action creating object should fail, not to create a new object. This usage will only check if the object has been not created. There is no need for passing fields of the object. You can specify it in this way:
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
it_behaves_like 'an action creating object', expect_failure: true
end
These shared examples allows you to check, if the request has updated an existing object. It requires to set the 'attributes' let block, with new attributes for the object. Also, it requires that the attributes will change, so the factory should use sequences for generating different values each time.
describe '#update' do
let!(:person) { create(:person) }
let(:attributes) { attributes_for(:person) }
let(:call_request) { patch :update, id: person.id, person: attributes }
it_behaves_like 'an action updating object', [:name, :age]
end
A name of updated model is evaluated from the described class' name, e.g.
If the described class is: UsersController
, the model's name is: 'user'.
If the object's class is different, you can override it:
describe '#update' do
let!(:person) { create(:person) }
let(:attributes) { attributes_for(:person) }
let(:call_request) { patch :update, id: person.id, person: attributes }
it_behaves_like 'an action updating object', [:name, :age] do
let(:model_name) { :admin }
end
end
Also, sometimes it's necessary to specify the new attributes:
describe '#update' do
let!(:person) { create(:person) }
let(:attributes) { attributes_for(:person) }
let(:call_request) { patch :update, id: person.id, person: attributes }
it_behaves_like 'an action updating object', [:name, :age] do
let(:new_attributes) { {admin: true} }
end
end
Sometimes, when for example you have roles in your system, you can specify, that the action updating object should fail - not to update the existing object. This usage will check if specified attributes have not changed after calling request. You can specify it in this way:
describe '#update' do
let!(:person) { create(:person) }
let(:attributes) { attributes_for(:person) }
let(:call_request) { patch :update, id: person.id, person: attributes }
it_behaves_like 'an action updating object', [:name, :age], expect_failure: true
end
These shared examples allows you to check, if the request has destroyed an existing object. It evaluates a name of object which should be destroyed from the described class name.
describe '#destroy' do
let!(:person) { create(:person) }
let(:call_request) { delete :destroy, id: person.id }
it_behaves_like 'an action destroying object'
end
When you want to specify a name of object which should be destroyed you can do it in two ways. Firstly, by specifying the object:
describe '#destroy' do
let!(:person) { create(:person) }
let!(:admin) { create(:admin) }
let(:call_request) { delete :destroy, id: person.id }
it_behaves_like 'an action destroying object' do
let(:object) { admin }
end
end
Secondly, by specifying name of the object which should be destroyed. This way, the shared example looks for an object, with given name:
describe '#destroy' do
let!(:person) { create(:person) }
let!(:admin) { create(:admin) }
let(:call_request) { delete :destroy, id: person.id }
it_behaves_like 'an action destroying object' do
let(:object) { :admin }
end
end
When you find that the action should fail - should not destroy the object, you can do it in this way:
describe '#destroy' do
let!(:person) { create(:person) }
let(:call_request) { delete :destroy, id: person.id }
it_behaves_like 'an action destroying object', expect_failure: true
end
These shared examples specify if the object should be handled with a service object.
It checks if the service object has been initialized and saved in the proper way.
The service object must respond to the save
method.
By default it expects the param for the initialize
method of the service object is an object which has a class
named by the service object, e.g. a service with class PersonCreator
expects an object of the Person
class.
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
let!(:schedule) { create(:schedule) }
it_behaves_like 'action handled with service object', PersonCreator
end
When you want to use custom params for the initialize
method of the service object:
describe '#create' do
let(:attributes) { attributes_for(:person) }
let(:call_request) { post :create, person: attributes }
let!(:schedule) { create(:schedule) }
it_behaves_like 'action handled with service object', PersonCreator do
let(:parameters) { [kind_of(Admin), admin: true] }
end
end