I am trying to create a functional test for paperclip 3.1.2 on rails 3.2.6 using FactoryGirl 3.0 and when i pass the file attachment my controller receives a string which is the file location and not the Paperclip::Attachment object.
My factory is:
include ActionDispatch::TestProcess
FactoryGirl.define do
factory :artist do
id 1
name 'MyString'
photo {fixture_file_upload("#{Rails.root}/test/fixtures/files/rails.png",'image/png')}
end
end
And my test is:
test "should create artist" do
assert_difference('Artist.count') do
post :create, :artist => { name: #artist.name, photo: #artist.photo }, :html => { :multipart => true }
end
assert_redirected_to artist_path(assigns(:artist))
end
In my controller this :
params[:artist][:photo].class.name
equals "String", but this passes when i add it to the test
assert #artist.photo.class.name == "Paperclip::Attachment"
My current workaround is to create the fixture_file_upload in the test :
test "should create artist" do
assert_difference('Artist.count') do
photo = fixture_file_upload("/files/rails.png",'image/png')
post :create, :artist => { name: #artist.name, photo: photo }, :html => { :multipart => true }
end
assert_redirected_to artist_path(assigns(:artist))
end
which works but creates a "Rack::Test::UploadedFile" and so I'm pretty confused why my factory returns a "Paperclip::Attachment" when the same method is called to create them both.
Thanks in advance for any light that you can shed on this as obviously I would like to use the factory rather than defining the fixture_file_upload outside of it.
Have you tried to replace
photo {fixture_file_upload("#{Rails.root}/test/fixtures/files/rails.png",'image/png')}
with
photo File.new("#{Rails.root}/test/fixtures/files/rails.png")
inside your factory.
Related
I currently have the following situation:
ChallengeRequestsController #new - creates a ChallengeRequest along with another model needing a recpient_id.
def create
#challenge_request = ChallengeRequest.new(challenge_params)
recipient = User.find(params.require(:recipient_id))
# Rest of method redacted.
end
def challenge_params
params.require(:challenge_request).permit(:action)
end
With this controller, I have an RSpec test as follows:
RSpec.describe ChallengeRequestsController, type: :controller do
describe "POST #create" do
context "with valid challenge request" do
let(:valid_challenge_request) { build(:challenge_request) }
context "with non-exisistent recpient id" do
it "throws an error" do
expect{
post :create, :params => {
:challenge_request => {
:challenge_request => valid_challenge_request
},
:recipient_id => 10000
}
}.to raise_error ActiveRecord::RecordNotFound
end
end
end
end
end
Just for reference, here's the Factory:
FactoryGirl.define do
factory :challenge_request do
action { Faker::LeagueOfLegends.champion }
end
end
All of this code works and the test passes, my question is how can I refactor this in a way that I don't need to use the ugly nesting in the request?
:challenge_request => {
:challenge_request => valid_challenge_request
}
When posting the request without this nesting:
post :create, :params => {
:challenge_request => valid_challenge_request,
:recipient_id => 10000
}
The challenge_request is empty when the ChallengeRequestController receives the request.
It would probably work if you change your valid_challenge_request to be a hash of attributes instead of the model itself.
let(:valid_challenge_request) { FactoryBot.attributes_for(:challenge_request) }
You're trying to send along an instance of a model as a parameter to a controller, and something is doing something (I can't find what's doing what to the model) to try and coerce it into something that a browser might send the controller. That conversion is turning the model into an empty string, which is not present or the value false and thus causes the require to throw the ParameterMissing
I want to test the mailer in my application to make sure it is doing what I want it to do.
class LessonMailer < ApplicationMailer
def send_mail(lesson)
#lesson = lesson
mail(to: lesson.student.email,
subject: 'A lesson has been recorded by your tutor')
end
end
This is my test in the spec/mailers directory
require "rails_helper"
RSpec.describe LessonMailer, :type => :mailer do
describe "lesson" do
let( :student ){ FactoryGirl.create :user, role: 'student', givenname: 'name', sn: 'sname', email: 'test#sheffield.ac.uk' }
let( :lesson ){ FactoryGirl.create :lesson, student_id: 2 }
let( :mail ){ LessonMailer.send_mail( lesson ).deliver_now
it "renders the headers" do
expect(mail.subject).to eq("A lesson has been recorded")
expect(mail.to).to eq(["to#example.ac.uk"])
expect(mail.from).to eq(["no-reply#example.ac.uk"])
end
it "renders the body" do
expect(mail.body.encoded).to match("A lesson form has been recorded")
end
end
end
I want to test that the 'send_mail' method is working the way I want it to however, I am getting this error. How do I go about solving this problem ? Thank you.
NoMethodError:
undefined method `email' for nil:NilClass
# ./app/mailers/lesson_mailer.rb:4:in `send_mail'
So, with FactoryGirl, you just need to instantiate the different objects that you need. Reading your code, it seems clear that a lesson has a student and that students have an email. So go ahead and create everything you need and then call your method. You can do something like this:
# Here's the student factory (for this use case, you'll probably want to make it more general)
FactoryGirl.define do
factory :user do
role 'student'
givenname 'name'
sn 'sname'
email 'test#sheffield.ac.uk'
end
end
# Here's your test
require "rails_helper"
RSpec.describe LessonMailer, :type => :mailer do
describe "lesson" do
let( :student ){ create :student, email: 'test_email#example.com' }
let( :lesson ){ create :lesson, student: student }
let( :mail ){ LessonMailer.send_mail( lesson ) }
it ' ... ' do
...
end
end
end
You'll need to let the test environment know that you want the emails to be delivered to the ActionMailer::Base.deliveries array. To do this, make sure that
config.action_mailer.delivery_method = :test
is set in your config/environments/test.rb
One last thing, I'm not sure if you'll need it, but you might have to call the mailer with, .deliver_now.
Like this:
let( :mail ){ LessonMailer.send_mail( lesson ).deliver_now }
... or it may not send. I can't remember off the top.
Let me know how it goes.
I am using FactoryGirl and populate unique attribute whenever the model is made. The problem with my model form is that there are only 4 different types available for form_type attribute. So I need to reset the sequence everytime I run tests. Like below, I user before do block to call FactoryGirl.reload. However, I saw an article saying it is anti-pattern to FactoryGirl. What is the best way to reset the sequence in FactoryGirl instead of calling FactoryGirl.reload before every test?
Here is my forms.rb Factorygirl file,
FactoryGirl.define do
factory :form do
association :user
sequence :form_type do |n|
Form.form_types.values[n]
end
end
end
Here is my form.rb model file:
class Form < ActiveRecord::Base
belongs_to :user, required: true
enum form_types: { :a => "Form A", :b => "Form B", :c => "Form C", :d => "Form D"}
validates :form_type, presence: true
validates :form_type, uniqueness: {scope: :user_id}
end
Here is my forms_controller_spec.rb file:
require 'rails_helper'
RSpec.describe FormsController, type: :controller do
login_user
let(:form) {
FactoryGirl.create(:form, user: #current_user)
}
let(:forms) {
FactoryGirl.create_list(:form , 3, user: #current_user)
}
let(:form_attributes) {
FactoryGirl.attributes_for(:form, user: #current_user)
}
describe "GET #index" do
before do
FactoryGirl.reload
end
it "loads all of the forms into #forms" do
get :index
expect(assigns(:forms)).to match_array(#forms)
end
end
end
Hm, it seems like the purpose of FG sequences is to ensure unique numbers. Or at least that's what I've used it for. You may be able to hack into FG if this is what you really want.
This question may help.
How can I reset a factory_girl sequence?
I'm working the a Documents class, trying to test it. I've defined the following factory:
require 'factory_girl'
FactoryGirl.define do
factory :document do
user_id '6315'
name 'Test doc'
description 'W9'
filename 'test_doc.pdf'
filetype 'file'
filesize 500
end
factory :invalid_doc, parent: :document do
filesize 5242900
end
end
with the following helper method to access the right attributes in the test:
def build_attributes(*args)
attrs = FactoryGirl.build(*args).attributes
attrs.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
paramify_values(attrs)
end
Before each test I run:
before(:each) do
login_as_admin
#doc = #user.documents.create(FactoryGirl.attributes_for(:document))
end
where #user is set in the login_as_admin macro. Within my test, I'm running this:
describe 'POST #create' do
it "should create a new document" do
expect{
post :create, document: build_attributes(:document, user_id: #doc.user_id)
}.to change(Document,:count).by(1)
end
it "should find the right user" do
post :create, document: build_attributes(:document, user_id: #doc.user_id)
assigns(:user).should eq(#user)
end
# some other tests...
end
The former test was suggested on this article, the latter is just what I think should be happening. The controller action is assigning the instance with the following:
#user = User.find(document[:user_id])
so, pretty standard. However, both of these tests throw the same error,
Failure/Error: post :create, document: build_attributes(:document, user_id: #doc.user_id)
NoMethodError:
undefined method `original_filename' for nil:NilClass
but I never call that method explicitly, so is it something FactoryGirl is calling? The model is described as follows:
attr_accessible :description, :filename, :filesize, :filetype, :name, :user_id
where :filename is just a string. What could be going wrong here? I'm not using paperclip to upload the files, just a file_field in the view. I grab the path and save the file to the production server in the controller, but never call this method.
Edit:
I suppose an actual controller description might help haha
def create
uploaded_file = params[:document][:file]
document = params[:document]
document.delete(:file)
#user = User.find(document[:user_id])
filepath = Rails.root.join('documents', #user.company_id.to_s, #user.id.to_s, uploaded_file.original_filename)
%x[ mkdir #{Rails.root.join('documents', #user.company_id.to_s)} ]
%x[ mkdir #{Rails.root.join('documents', #user.company_id.to_s, #user.id.to_s)} ]
File.open(filepath, 'wb') do |file|
file.write(uploaded_file.read)
end
document[:filesize]= File.size(filepath)
document[:filetype]= File.ftype(filepath)
document[:filename] = uploaded_file.original_filename
d =Document.new(document)
d.save
redirect_to :action => 'show', :id => user.id
end
Please keep in mind I'm sure there are many things wrong with this method. I'm trying to refactor it and test as I go. For the moment, all I'm trying to do is get past this first hiccough, the original_filename method is being called somewhere, and I don't define it myself. Can anyone see why/where?
original_filename is a method on an uploaded file, see the rack documentation.
filepath = Rails.root.join('documents', #user.company_id.to_s, #user.id.to_s, uploaded_file.original_filename)
and
document[:filename] = uploaded_file.original_filename
In the controller are getting the original filename, since when a file gets uploaded it gets an ugly temp filename for storage you want to use the original filename to make it readable and accurate.
Consider using the fixture_file_upload helper in rspec. Here is an example spec:
expect {
post :create, document: attributes_for(:document, user_id: #doc.user_id, file: fixture_file_upload('spec/assets/documents/test_doc.pdf', 'appliation/pdf'))
}.to change(Document, :count).by(1)
And place a test pdf in spec/assets/documents/test_doc.pdf
You can use
Rack::Multipart::UploadedFile.new(path)
for your test.
I'm sorry, but this is beginning to feel like kicking myself in the head. I'm completely baffled by RSpec. Have watched video after video, read tutorial after tutorial, and still I'm just stuck on square one.
=== here is what I'm working with
http://github.com/fudgestudios/bort/tree/master
=== Errors
F
1)
NoMethodError in 'bidding on an item should work'
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
spec/controllers/auction_controller_spec.rb:16:
spec/controllers/auction_controller_spec.rb:6:
Finished in 0.067139 seconds
1 example, 1 failure
=== here is my controller action
def bid
#bid = Bid.new(params[:bid])
#bid.save
end
=== here is my test
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
before(:each) do
#user = mock_user
stub!(:current_user).and_return(#user)
end
it "should work" do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
assigns[:bid].should be_new_record
end
end
=== spec_helper
http://github.com/fudgestudios/bort/tree/master/spec/spec_helper.rb
It's very disheartening to wake for work at 3 a.m. and accomplish nothing for the day. Please understand.
You've got a couple of things backwards in before(:each). Seeing as the example is specifying that the post should increase the count by 1, you're dealing with real records and there is no reason for stubbing anything at all. Also, at this point, since there is only one example, there is no reason to have a before block. I'd do it this way:
describe ItemsController, "bidding on an item" do
fixtures :users
it "should create a new Bid" do
login_as :quentin
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end
One thing I'd recommend is creating these things VERY granularly for now until you understand them better. Start with the expectation (post should change bid count), run the spec and let the failure message guide you to add whatever else you need in the spec or in the code.
Jesse,
It'll still pass if you comment out the 2nd two lines of before(:each), which are having no impact on the "should create a new Bid" example.
The lambda keyword creates an arbitrary block of code that is not executed when you define it, but is actually an object you can assign to a variable and execute later:
the_post = lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
At this point that code is not executed, but we can refer to it with the 'the_post' variable. Now we can send it 'should', followed by 'change ...', like this:
the_post.should change(Bid, :count).by(1)
When this line is executed, a few things happen. The material to the right of 'should' is evaluated first, initializing an rspec matcher object with some instructions. That matcher is the argument to 'should' - the equivalent of this:
matcher = change(Bid, :count).by(1)
the_post.should(matcher)
The 'should' method is called on the_post, which is the code block (that still hasn't been executed). Under the hood, the 'should' method passes self (the_post) to the matcher, so the matcher now has everything it needs to evaluate the example.
The matcher calls Bid.count and records the value. Then it executes the block (the_post), and then calls Bid.count a second time and compares it to the value it recorded earlier. In this case, since we're looking for Bid.count to change by 1 (positive is implicit here - increase by 1), if that's what happens the matcher stays silent and the example passes.
If the values are the same, or differ by some value other than 1, the example will fail. You can see that work if you change the expectation to by(2) instead of by(1).
HTH,
David
EDIT: you shouldn't expect Bid.count to increment when using a mock object. Mantra I forgot: caffeine before code.
Just commenting out the lines, for now, so the original is still there.
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "POST to bid_controller" do
controller_name :items
before(:each) do
##bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#Bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
# ... more specs
end
Try to write as small specs as possible, write your setences in such a way as to make it obvious what you should be verifying in that spec. For example, how I changed yours from it "should work" to it "should create a new Bid". If there's more to that controller, write a new spec
for each small piece of functionality.
If you do end up needing mock users, there are some helpers for restful_authentication that make it easier. First create a user fixture in
RAILS_ROOT/spec/fixtures/users.yml, like this:
quentin:
login: quentin
email: quentin#example.com
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
created_at: <%= 5.days.ago.to_s :db %>
activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b
activated_at: <%= 5.days.ago.to_s :db %>
name: "Quentin"
Then in your spec you will be able to write the following and have your current_user method and all the other parts of restul_authentication
behave as you would expect them to at runtime.
login_as :quentin
# .... the rest of your spec
As an example of a few more specs I might add as a couple more examples:
def do_post
# extracting the method under test, so I don't repeat myself
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
it "should create a new Bid" do
lambda do
do_post
end.should change(Bid, :count).by(1)
end
it "should assign the Bid to the proper auction" do
#bid.should_receive(:auction_id=).with(1) # test this, I believe doing Bid.new(params[:bid]) sets the id directly not sets the model
do_post
end
it "should assign the Bid the proper points" do
#bid.should_receive(:point=).with(1)
do_post
end
While I don't quite understand what's going on. (with stubs and the lambda)....
for
def bid
#bid = Bid.new params[:bid]
#bid.save
end
The following passes !!
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
fixtures :users
before(:each) do
#user = login_as :quentin
#bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
#Bid.stub!(:save).and_return(true)# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end