I created a movie review website which allows a logged in user to add, edit and delete a movie as well as leave reviews for each movie. I have also implemented a mailer for my contact form that sends a 'fake email' (displayed on the console only).
This is my first time working with Ruby so I am unsure on how to test my controller and method for contacts. Any form of advice will be much appreciated.
contacts_controller.rb:
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(params[:contact])
#contact.request = request
if #contact.deliver
flash.now[:notice] = 'Thank you for your message. We will contact you soon!'
else
flash.now[:error] = 'Cannot send message.'
render :new
end
end
end
contact.rb:
class Contact < MailForm::Base
attribute :name, :validate => true
attribute :email, :validate => /\A([\w\.%\+\-]+)#([\w\-]+\.
attribute :message
attribute :nickname, :captcha => true
# Declare the e-mail headers. It accepts anything the mail method
# in ActionMailer accepts.
def headers
{
:subject => "My Contact Form",
:to => "your_email#example.org",
:from => %("#{name}" <#{email}>)
}
end
end
routes:
contacts GET /contacts(.:format) contacts#new
POST /contacts(.:format) contacts#create
new_contact GET /contacts/new(.:format) contacts#new
My testing so far:
require 'test_helper'
class ContactsControllerTest < ActionController::TestCase
include Devise::Test::ControllerHelpers
test "should get contact" do
get :new
assert_response :success
end
end
You can read more information here http://edgeguides.rubyonrails.org/testing.html#testing-your-mailers
require 'test_helper'
class ContactsControllerTest < ActionDispatch::IntegrationTest
test "ActionMailer is increased by 1" do
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
post contacts_url, params: { name: 'jack bites', email: 'bite#jack.org', message: 'sending message', nickname: 'jackbites' }
end
end
test "Email is sent to correct address" do
post contacts_url, params: { name: 'jack bites', email: 'bite#jack.org', message: 'sending message', nickname: 'jackbites' }
invite_email = ActionMailer::Base.deliveries.last
assert_equal 'bite#jack.org', invite_email.to[0]
end
end
Related
I want it to redirect to the user page for the just created post.
But, I'm facing the following error:
Failure/Error: expect(response).to redirect_to user_path(user.id)
Expected response to be a <3XX: redirect>, but was a <200: OK>
Response body:
Here is my posts_controller_spec.rb
describe "POST #create" do
context "parameter is reasonable" do
let(:valid_attributes) {
FactoryBot.attributes_for(:post)
}
it "redirect user page" do
post :create, params: { user_id: user.id, post: valid_attributes }
expect(response).to redirect_to user_path(user.id)
end
end
end
My factories/users.rb
FactoryBot.define do
factory :admin_user, class: User do
name 'Alice'
email 'test#gmail.com'
intro 'rubyなう'
password 'Testtesttest'
admin true
end
factory :user, class: User do
name 'Bob'
email 'test#gmail.com'
intro 'rubyなう'
password 'Testtesttest'
end
end
My factories/posts.rb
FactoryBot.define do
factory :post, class: Post do
content 'a' * 140
image Rack::Test::UploadedFile.new(File.join(Rails.root, 'app/assets/images/tokyo.jpg'))
user
end
end
My posts_controller.rb
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = 'succeed in posting'
redirect_to current_user
else
render 'new'
end
end
Any ideas?
In my Rails 4 app I have this update action:
class UsersController < ApplicationController
...
def update
current_email = #user.email
new_email = user_params[:email].downcase
if #user.update_attributes(user_params)
if current_email != new_email
#user.email = current_email
#user.new_email = new_email.downcase
#user.send_email_confirmation_email
flash[:success] = "Please click the link we've just sent you to confirm your new email address."
else
flash[:success] = "User updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
...
end
It basically makes sure that a user cannot simply save any new email address. He will have to confirm it first by clicking on a link in an email we send to him.
This works great, however, for some reason I haven't found a way to test it.
The following RSpec test keeps failing no matter what I do:
it "changes the user's new_email attribute" do
#user = FactoryGirl.create(:user, :email => "john#doe.com")
patch :update, :id => #user, :user => FactoryGirl.attributes_for(:user, :email => "new#email.com")
expect(#user.reload.new_email).to eq("new#email.com")
end
#user.new_email is always nil and the test always fails. What am I missing here?
Re-factoring my update action wouldn't be a problem at all. Maybe there's a better way? Thanks for any help.
I would write the spec like so:
let(:user) { FactoryGirl.create(:user, email: "john#doe.com") }
it "changes the user's new_email attribute" do
expect do
patch :update, id: #user, user: FactoryGirl.attributes_for(:user, email: "new#email.com")
user.reload
end.to change(user, :new_email).from("john#doe.com").to("new#email.com")
end
When it comes to the controller action itself the problem is that the new_email property is never saved to the database, besides that its kind of a mess. You can clean it up by using ActiveRecord::Dirty which tracks attribute changes in the model:
class User < ApplicationRecord
# updates user with attrs but moves a new email to the `new_email`
# column instead
def update_with_email(attrs, &block)
update(attrs) do |record|
if record.email_changed?
record.new_email = record.email.downcase
record.restore_attribute!(:email)
end
# keeps the method signature the same as the normal update
yield record if block_given?
end
end
end
Putting this business logic in the model also lets you test it separatly:
RSpec.describe User, type: :model do
describe "#update_with_email" do
let(:user) { FactoryGirl.create(:user) }
it "does not change the email attribute" do
expect do
user.update_with_email(email: ”xxx#example.com”)
user.reload
end.to_not change(user, :email)
end
it "updates the new_email" do
expect do
user.update_with_email(email: ”xxx#example.com”)
user.reload
end.to change(user, :new_email).to('xxx#example.com')
end
end
end
This lets you keep the controller nice and skinny:
def update
if #user.update_with_email(user_params)
if #user.new_email_changed?
#user.send_email_confirmation_email
flash[:success] = "Please click the link we've just sent you to confirm your new email address."
else
flash[:success] = "User updated."
end
# You probably want to redirect the user away from the form instead.
redirect_to edit_user_path(#user)
else
render :edit
end
end
I'm writing an RSpec called Leads controller spec. In that I'm writing a test for create action of lead controller. Now my lead controller calls Project model to create an object(Project) which also creates Contact object and assigns it to project. but when I try to test whether my Project model creating a Contact object or no, The tests are getting failed. I don't know why my contact object is not getting created:(
My leads_controller_spec.rb
describe "POST #create" do
it "should create a contact too" do
my_lead = Fabricate(:project, id: Faker::Number.number(10))
expect{
post :create, project: my_lead.attributes
}.to change(Contact, :count).by(1)
end
it "should be equal to last created contact" do
my_lead = Fabricate(:project, id: Faker::Number.number(10))
post :create, project: my_lead.attributes
expect(Project.last.contact).to eq(Contact.last)
end
end
leads_controller.rb
def create
if #lead = Project.add_new_lead(lead_params)
#lead.create_activity :create_new_lead, owner: current_user
puts "My lead in create action: #{#lead.inspect}"
else
respond_to do |format|
format.html { redirect_to :back, :alert => "Email is already Taken"}
end
end
respond_to do |format|
format.html { redirect_to leads_path }
end
end
Project.rb
def add_new_lead(inputs, data = {})
if !Contact.where(email: inputs[:email]).present?
contact = Contact.create(phone: inputs[:phone], email: inputs[:email], fullname: inputs[:fullname])
project = Project.create(name: inputs[:fullname], flat_status: inputs[:flat_status], flat_type: inputs[:flat_type], flat_area: inputs[:area], location: inputs[:locality], address: inputs[:site_address], customer_type: inputs[:customer_type])
project.contact = contact
project.save
project
else
return nil
end
end
contact_fabricator.rb
require 'faker'
Fabricator(:contact) do
email { "email_#{Kernel.rand(1..30000)}#prestotest.com" }
fullname "project#{Kernel.rand(1..30000)}"
address "address#{Kernel.rand(1..30000)}"
end
project_fabricator.rb
require 'faker'
Fabricator(:project) do
contact
end
contact.rb
field :phone, type: String
field :email, type: String
field :fullname, type: String
field :status, type: String, default: "DEFAULT"
field :address, type: String
field :new_address, type: String
field :other_data, type: Hash, default: {}
validates_presence_of :email
validates_uniqueness_of :email, :message => "Email already taken"
You are using the factories wrong in your spec. You want to use Fabricate.attributes_for(:project) instead of creating a record and taking its attributes as that will cause any uniqueness validations to fail.
require 'rails_helper'
describe ProjectsContoller
describe "POST #create" do
# don't forget to test with invalid input!
context "with invalid attributes" do
let(:attributes) { { foo: 'bar' } }
it "does not create a project" do
expect do
post :create, attributes
end.to_not change(Project, :count)
end
end
context "with valid attributes" do
let(:attributes) { Fabricate.attributes_for(:project) }
it "creates a project" do
expect do
post :create, attributes
end.to change(Project, :count).by(+1)
end
end
end
end
When it comes to the rest of your controller you should very likely be using nested attributes instead as you are not handling the case where the validation of contact fails. You're also using Contact.create when you should be using Contact.new.
Note that this is a pretty advanced topic and you might want to learn the basics first and revisit it later.
class Project < ActiveRecord::Base
belongs_to :contact
accepts_nested_attributes_for :contact
validates_associated :contact
end
class Contact < ActiveRecord::Base
has_many :projects
end
class ProjectsController < ApplicationController
def new
#project = Project.new
end
def create
#project = Project.new(project_params)
if #project.save
format.html { render :new } # don't redirect!
else
format.html { redirect_to leads_path }
end
end
private
def project_params
params.require(:project).permit(:foo, :bar, contact_attributes: [:email, :name, :stuff, :more_stuff])
end
end
<%= form_for(#project) do |f| %>
<%= f.text_field :foo %>
# These are the attributes for the contact
<%= fields_for :contact do |pf| %>
<%= pf.text_field :email %>
<% end %>
<% end %>
I testing my app for create a new user car, later that the user creates the new car the app must redirect to the user_car_path (I post my routes):
user_cars GET /users/:user_id/cars(.:format) cars#index
POST /users/:user_id/cars(.:format) cars#create
new_user_car GET /users/:user_id/cars/new(.:format) cars#new
edit_user_car GET /users/:user_id/cars/:id/edit(.:format) cars#edit
user_car GET /users/:user_id/cars/:id(.:format) cars#show
PUT /users/:user_id/cars/:id(.:format) cars#update
DELETE /users/:user_id/cars/:id(.:format) cars#destroy
so I'm testing my app with this rspec:
describe "POST 'create' car" do
describe "car created success" do
before(:each) do
#user = User.create!(:email => "foo#example.com", :password => "foobar", :password_confirmation => "foobar" )
#car = Car.create!(:brand => "example", :color => "foobar", :model => "foobar", :year =>"2012")
end
it "should create a car" do
lambda do
post :create, :cars => #car, :user_id => #user.id
end.should change(Car, :count).by(1)
end
it "should redirect to the user cars page" do
post :create, :cars => #car, :user_id => #user.id
response.should redirect_to user_car_path(#user, #car)
end
end
end
but i got 2 errors
Failures:
1) CarsController POST 'create' car car created success should create a car
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/car_controller_spec.rb:20
2) CarsController POST 'create' car car created success should redirect to the user cars page
Failure/Error: response.should redirect_to user_car_path(#user, #car)
Expected response to be a redirect to <http://test.host/users/115/cars/40> but was a redirect to <http://test.host/users/115/cars/new>.
# ./spec/controllers/car_controller_spec.rb:27
but my app works normally; here is my CarController
class CarsController < ApplicationController
....
def create
#user = User.find(params[:user_id])
#car = #user.cars.build(params[:car])
if #car.save
redirect_to user_car_path(#user, #car), :flash => { :notice => " car created!" }
else
redirect_to new_user_car_path ,:flash => { :notice => " sorry try again :(" }
end
end
def show
#user = User.find(params[:user_id])
#car = #user.cars.find(params[:id])
end
....
You are creating the car in the database, rather than just creating a car object in the before(:each). Also you're passing the param as :cars rather than :car. Finally, I'd also personally use let. Try this.
describe "POST 'create' car" do
let(:user) { User.create!(:email => "foo#example.com", :password => "foobar", :password_confirmation => "foobar" ) }
let(:car) { Car.new(:brand => "example", :color => "foobar", :model => "foobar", :year =>"2012")}
it "should create a car" do
lambda do
post :create, :car => car, :user_id => user.id
end.should change(Car, :count).by(1)
end
it "should redirect to the user cars page" do
post :create, :cars => car, :user_id => user.id
# note: car doesn't have an ID, you have to fetch it from the db to get an id
response.should redirect_to user_car_path(user.id, user.cars.last.id)
end
end
As a final note, you will want to look into Factory Girl
Then you could do this instead:
let(:user){ FactoryGirl.create(:user) }
let(:car) { FactoryGirl.build(:car) } # note: BUILD not CREATE
I think it's because your validation fail. To know if it fails do this, before save in your controller :
puts #car.valid?
If it's false, your test will fail and it's normal. To set to true you can do this:
before(:each) do
...
Car.any_instance.stub(:valid?).and_return(true)
end
You can also use stubs and mocks to return instances for User.find and Car.build. See the doc : https://www.relishapp.com/rspec/rspec-mocks/v/2-6/docs/method-stubs
I think you want the following (:cars => :car and use params that would make the car creation in the controller valid. I am confused why you are creating the car in your spec because this seems to be a test of car creation)
it "should create a car" do
lambda do
post :create, :car => {:brand => "example", :color => "foobar", :model => "foobar", :year =>"2012"}, :user_id => #user.id
end.should change(Car, :count).by(1)
end
it "should redirect to the user cars page" do
post :create, :car => {:brand => "example", :color => "foobar", :model => "foobar", :year =>"2012"}, :user_id => #user.id
response.should redirect_to user_car_path(#user, #car)
end
I test password recovery, but there are errors.
Rspec study recently.
code (User Controller)
def forgot
if request.post?
user = User.find_by_email(params[:user][:email])
if user
user.create_reset_code
end
flash[:notice] = t('helpers.notice_email')
render :template => "sessions/new"
end
end
rspec test
it "POST 'reset page'" do
User.should_receive(:find_by_email).with({:email => #user.email})
post :forgot, :user => {"email" => #user.email}
end
What am I doing wrong in the test?
User.should_receive(:find_by_email).with(#user.email)