What is the proper way to test 'create' controller actions?
How about:
it "creates article" do
article_params = FactoryGirl.attributes_for(:article)
expect { post :create, :article => article_params }.to change(Article, :count).by(1)
end
How to call the create action from the controller in RSpec
Request specs are integration tests, using something like Capybara to visit pages as a user might and perform actions. You wouldn't test a create
action from a request spec at all. You'd visit the new item path, fill in the form, hit the Submit button, and then confirm that an object was created. Take a look at the Railscast on request specs for a great example.
If you want to test the create action, use a controller spec. Incorporating FactoryGirl, that would look like this:
it "creates a post" do
post_attributes = FactoryGirl.attributes_for(:post)
post :create, post: post_attributes
response.should redirect_to(root_path)
Post.last.some_attribute.should == post_attributes[:some_attribute]
# more lines like above, or just remove `:id` from
# `Post.last.attributes` and compare the hashes.
end
it "displays new on create failure" do
post :create, post: { some_attribute: "some value that doesn't save" }
response.should redirect_to(new_post_path)
flash[:error].should include("some error message")
end
These are the only tests you really need related to creation. In your specific example, I'd add a third test (again, controller test) to ensure that the appropriate DifferentModel
record is created.
Rspec testing controller action create logic
In both cases, when a customer is found or a new customer is created, the response
will be a redirect_to
and this is why your tests are passing. Note that since you are using build
in your test, this is not saving the customer in the database and therefore your test is always creating a new customer
and Customer.find_by(phone: customer_params[:phone])
is always evaluating to nil
.
The best way to test your create
action is by comparing the count of Customer
instances before and after the request is made. Here is a how you can improve upon your tests.
let(:customer) { FactoryGirl.create(:customer, phone: '999') }
context 'user not found by phone' do
it 'creates a new customer' do
expect{
post :create, customer: attributes_for(:customer, phone: "999")
}.to change(Customer, :count).by 1
end
it 'redirects to the customer path' do
post :create, customer: attributes_for(:customer, phone: "999")
customer = Customer.last
expect(response).to redirect_to(customer)
#this is another differentiator between the case where a customer is
#found and when one is created
expect(flash[:notice).to eq "Customer was successfully saved"
end
end
context 'user not found by phone' do
before(:each) do
#here we create the customer before each test in this context
customer
end
it 'does not create a new customer' do
expect{
post :create, customer: attributes_for(:customer, phone: customer.phone)
}.not_to change(Customer, :count)
end
it 'redirects to the customer path' do
post :create, customer: attributes_for(:customer, phone: customer.phone)
customer = Customer.last
expect(response).to redirect_to(customer)
#this is another differentiator between the case where a customer is
#found and when one is created
expect(flash[:notice).to be_nil
end
end
What exactly should I test for a controller action method that returns a View with ViewModel
It depends on what your controller action is supposed to do, but you should for example verify that the right view was returned, whether the right view model was returned and assert the expected state of the view model. Please refer to the docs for more information about how to unit test controllers:
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/unit-testing/creating-unit-tests-for-asp-net-mvc-applications-cs
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/testing?view=aspnetcore-2.1
Keep in mind that the controller logic should be minimal though. Your business logic should be implemented in the model(s).
RSpec test controller action create not affect test database
It's because you have this line:
config.use_transactional_fixtures = true
The name of this setting is a bit misleading. What it really means in
Rails is "run every test method within a transaction." In the context
of rspec-rails, it means "run every example within a transaction."The idea is to start each example with a clean database, create
whatever data is necessary for that example, and then remove that data
by simply rolling back the transaction at the end of the example.
See: https://relishapp.com/rspec/rspec-rails/docs/transactions
RSpec test for create action of a controller for a nested resource
For a nested resource you need to construct the setup data and the post in such a way as to identify the parent article when posting the child comment.
One approach is to setup Factory Girl associations correctly and then ensure the parent element is set when creating the child attributes. It would look something like this:
In the comment factory:
FactoryGirl.define do
Factory :comment do
comment "My comment"
article
end
end
By calling article, and making sure that there is a valid factory called :article
then FactoryGirl will create an article when a comment is created. To make the tests flow well we should actually be specific about which article
is used when the comment
is created, so now that the Factory is in place we use the following in the spec.
@comment_attributes = FactoryGirl.attributes_for(:comment, :article_id => @article)
This will build comment attributes which are automatically attached to @article. The final piece is then to construct the post, making sure that we include the parent and the child.
When a nested resource is posted it expects params for both the parent resource and the child. In rspec we can provide this in the post as follows:
post :create, :article_id => @article, :comment => @comment_attributes
This should link up all the pieces correctly.
Testing create Action using RSpec in Rails
[For Future Reader] I got it to work by doing the fooling in leave_controller_spec.rb file.
describe 'POST#create' do
context 'with valid attributes' do
let(:valid_attribute) do
attributes_for(:leave,
user_id: 2,
team_lead_id: 3,
fiscal_year_id: 2,
start_day: '2018-10-10'.to_date,
end_day: '2018-10-10'.to_date,
reason: 'Sick',
status: 2)
end
it 'saves the new leave in the database' do
expect do
post :create, params: {leave: valid_attribute}
end.to change(Leave,:count).by(1)
end
it 'redirects to leave#index' do
render_template :index
end
end
Related Topics
Group_By in Rails by 2 or More Attributes
How to Delete Special Characters
Error Message "Xcode Alone Is Not Sufficient on Sierra"
How to Validate the Date Such That It Is After Today in Rails
Why Is This String Key in a Hash Converted to a Symbol
How to Return a Value from a Thread in Ruby
Build Hash from Collection of Activerecord Models
How to Get Files Count in a Directory Using Ruby
Ruby, Check If Date Is a Weekend
How to Replace Multiple Newlines in a Row with One Newline Using Ruby
What Is the Preferred Way (Better Style) to Name a Namespace in Ruby? Singular or Plural
Rails: Render Doesn't Work, Still Get 'Template Is Missing'
Rails 3.2.2 (Or 3.2.1) + Postgresql 9.1.3 + Ubuntu 11.10 Connection Error
Gem File with Git Remote Failing on Heroku Push
What Are the Ruby's Object#Taint and Object#Trust Methods