Rails - Functional test failing because of validation - ruby-on-rails

I can't for the life of me figure out what's going wrong here. This is the situation:
I have a document model using paperclip for pdf attachments
The functional test uploads a document and fails
The reason for this is my validation that it is a pdf file - somehow that validation fails
However, the file is a pdf and the validation should not fail
The validation only fails in the test - doing it manually by uploading the file it complains about works absolutely fine
Here is my failing test (the count is not increased by one):
test "should create document" do
assert_difference('Document.count') do
post :create, document: { pdf: fixture_file_upload("../files/document_test_file.pdf"), language: #document.language, published_on: #document.published_on, tags: #document.tags, title: #document.title, user_id: #user }
end
assert_redirected_to document_path(assigns(:document))
end
This is my validation in the document model:
def document_is_a_pdf
if !self.pdf.content_type.match(/pdf/)
errors.add(:pdf, "must be a pdf file")
false
end
end
If I do not call that validation in the model, the test runs fine. What am I doing wrong here?

I know this is an old question but if anyone still needs help,
From http://apidock.com/rails/ActionController/TestProcess/fixture_file_upload
fixture_file_upload(path, mime_type = nil, binary = false)
Fixture file upload method by default will set the mime type as nil so simply changing the mime type as below will correct this
fixture_file_upload("../files/document_test_file.pdf", 'application/pdf')

I found the problem. Somehow, while testing, the content type could not be determined. That is why the validation of the content type failed and the test did not pass.
I added the content type to the accessible attributes inside my document model and inserted the content type in the test (second attribute inside the document hash):
test "should create document" do
assert_difference('Document.count') do
post :create, document: { pdf: fixture_file_upload("../files/document_test_file.pdf"), pdf_content_type: "application/pdf", language: #document.language, published_on: #document.published_on, tags: #document.tags, title: #document.title, user_id: #user }
end
assert_redirected_to document_path(assigns(:document))
end

Related

Rspec : stubbing ActiveStorage download method

I work on a system that stores cached data on S3 with ActiveStorage before using it for something else. In my spec, I want to stub the download method of this file, and load a specific file for testing purpose.
allow(user.cached_data).to receive(:download)
.and_return(read_json_file('sample_data.json'))
(read_json_file is a spec helper that File.read then JSON.parse a data file.)
I get this error :
#<ActiveStorage::Attached::One:0x00007f9304a934d8 #name="cached_data",
#record=#<User id: 4, name: "Bob", email: "bob#email.com",
created_at: "2019-08-22 09:11:16", updated_at: "2019-08-22 09:11:16">,
#dependent=:purge_later> does not implement: download
I don't get it, the docs clearly say that this object is supposed to implement download.
Edit
As suggested by Jignesh and Stephen, I tried this :
allow(user.cached_data.blob).to receive(:download)
.and_return(read_json_file('sample_data.json'))
and I got the following error :
Module::DelegationError:
blob delegated to attachment, but attachment is nil
user is generated by FactoryBot, so I'm currently trying to attach my cached_data sample file to that object.
My factory looks like that :
FactoryBot.define do
factory :user
name { 'Robert' }
email { 'robert#email.com' }
after(:build) do |user|
user.cached_data.attach(io: File.open("spec/support/sample_data.json"), filename: 'sample.json', content_type: 'application/json')
end
end
end
But when I add that after build block to the factory, I get the following error :
ActiveRecord::LockWaitTimeout:
Mysql2::Error::TimeoutError: Lock wait timeout exceeded
Maybe it's another Stackoverflow question.
As other have pointed out in the comments, #download is implemented on the ActiveStorage::Blob class, not ActiveStorage::Attached::One. You can download the file with the following:
if user.cached_data.attached?
user.cached_data.blob.download
end
I added the check to ensure cached_data is attached because blob delegates to the attachment and would fail if not attached.
Here is the documentation for #download.

Using Rails 5 and minitest, how can I access a record that was created in the test?

I have this test that was passing before I switched the Note table to use UUID:
test "SHOULD create note AND redirect to Notes#index(SINCE null folder_id) WHEN only body is provided IF logged in as account:owner" do
sign_in #user
assert_difference('#user.account.notes.count') do
post notes_url(#user.account.hash_id), params: { note: { body: #note_by_user_no_folder.body } }
end
assert_redirected_to notes_url(#user.account.hash_id, anchor: Note.last.id)
end
But after switching to UUID, I'm getting the following failure error:
Failure:
NotesControllerTest#test_SHOULD_create_note_AND_redirect_to_Notes#index(SINCE_null_folder_id)_WHEN_only_body_is_provided_IF_logged_in_as_account:owner [/Users/chris/Dropbox/Repositories/keepshelf/test/controllers/notes_controller_test.rb:117]:
Expected response to be a redirect to <http://www.example.com/11111111/notes#9dc409ff-14cc-5f64-8f5f-08e487f583ee> but was a redirect to <http://www.example.com/11111111/notes#34dac6b7-46af-4c5c-bff7-760ffa77edf6>.
Expected "http://www.example.com/11111111/notes#9dc409ff-14cc-5f64-8f5f-08e487f583ee" to be === "http://www.example.com/11111111/notes#34dac6b7-46af-4c5c-bff7-760ffa77edf6".
I take this as that since the UUID's do not follow an order, that the new Note I am creating in the "post notes_url(..." does not end up being the "last" one that Note.last.id finds.
How can I set the anchor: to record_that_was_just_created.id ?
I figured this out. apparently there is a way to access the controller's instance variables in the test so I got it to work by changing the assert_redirected_to to
assert_redirected_to notes_url(#user.account.hash_id, anchor: controller.instance_variable_get(:#note).id)
Late answer here, I'm glad you got the result you need!
... in case anyone else reads this looking for the test practices ...
The short answer is - you should always know what data is being used in the test ... because you make it. The most common 3 ways ...
In the setup method where you
Load a fixture #account = Accounts(:first) (needs matching entry in yaml file)
Call factorybot gem (see thoughtbot's page for how to)
Explicitly write #account = #user.account.build(field: value, field2: value)
So, you have the literal answer to your question - it's bad practice to test against uncertain data - Shaunak was saying that too.
The above is probably enough - but your detail oriented testing should be in unit tests - where you are not calling against db & making your tests the slowest possible version. Your integration level stuff should just test that creation was successful - you already know the data going in there works.
For the best experience you probably want unit tests something from the 3 methods above in setup & use #account in the test like so to test against the validations you are about to create ...
So the unit test would be ...
setup
#account = #user.account.build # avoid hitting db unnecessarily versus create
#account.field = value
#account.field2 = value2
end
def test_details_pass_activerecord_validations
signin #user
#account.test_specific_fields = value # stuff not all the tests use
assert #account.valid? # trigger all the activerecord validations, but not db
end
From there your integration ...
test "SHOULD create note AND redirect to Notes#index(SINCE null folder_id) WHEN only body is provided IF logged in as account:owner" do
# Your setup method above should have most of the intialization
# ... basically #account = #user.account .note would be setup, but not inserted
signin #user
assert_difference('#user.account.notes.count') do
post notes_url(#account), params: { note: { body: #account.note.body } }
end
assert_redirected_to notes_url(#account, anchor: #account.whatever_field)
end

RSpec Controller test converting hash into String

I have a controller spec for my application, which tests the create method on a controller. The create action actually works fine, but the spec is failing. It seems that it is auto converting the the hash POST param into a string.
let(:coupon) { attributes_for(:coupon) }
describe 'POST #create' do
it 'should create a new coupon from params' do
expect {
post :create, :coupon => coupon
}.to change(Coupon, :count).by(1)
end
end
Now, If I do puts coupon it is generating a valid hash of data, and the type is hash. For some reason the controller is receiving a string for params[:coupon]. Only in rspec testing does this happen, when I test in the browse with a POST form it works perfectly fine.
Rspec throws the following message:
NoMethodError:
undefined method `permit' for #<String:0x00000005062700>
Did you mean? print
and if I do puts params[:coupon].class in the controller in rspec it gives me String. Why might it be converting my hash into a string for the POST request, and how can I prevent this ?
I am using Rails 5.0.0 and rspec 3.5.1
This exact same behavior showed up for me recently when testing a JSON API endpoint. Originally I had this as my subject:
subject { put :my_endpoint, **input_args }
and an integer value in input_args was getting translated into a string. The fix was to add format: 'json' as an additional keyword argument to put:
subject { put :my_endpoint, **input_args, format: 'json' }
It seems that it was an issue with the gem open_taobao somehow transforming my post requests in tests.

Issues with testing in rails

I am writing tests for my application. I have written quite a few tests and they were working fine. However, two of my tests keep failing and I am not sure why that is. The first test is a unsuccessful editing of a form.
The test is given below
test "unsuccessfull editing of the scoreboard" do
log_in_as(#user)
get edit_scoreboard_path(#scoreboard)
assert_template 'scoreboards/edit' (doesn't work)
patch scoreboard_path(#scoreboard), scoreboard: { name_of_scoreboard: "a"* 51,
name_of_organization: "b"*60,
name_of_activity: "c"*60 }
assert_template 'scoreboard/edit' (doesn't work)
end
The error associated with this test is given below.
ScoreboardEditTest#test_unsuccessfull_editing_of_the_scoreboard [/home/ubuntu/workspace/test/integration/scoreboard_edit_test.rb:13]:
expecting <"scoreboards/edit"> but rendering with <[]>
The controller for the following test is also given below.
def update
#scoreboard = Scoreboard.find(params[:id])
if #scoreboard.update_attributes(scoreboard_params)
redirect_to #scoreboard
flash[:success] = "Updated Successfully"
else
render 'edit'
end
end
After you get the edit_scoreboard_path, the edit scoreboard template should show up. I am not exactly sure why it gives me the error. I have the exact same thing with the user model and its working just fine. I think I am missing something in my understanding of it works.
The second test is a valid creation of a scoreboard. The test is given below.
test "valid creation of the scoreboard with passing validations" do
log_in_as(#user)
get new_scoreboard_path
assert_difference 'Scoreboard.count', 1 do
post scoreboards_path, scoreboard: {name_of_scoreboard: "abc",
name_of_organization: "def",
name_of_activity: "ghi" }
end
assert_redirected_to scoreboard_path(#scoreboard) (doesn't work)
assert_equal 'Scoreboard created successfully', flash[:success]
end
It redirecting to the wrong scoreboard id. In the fixtures I have the id set as 1. The error message is given below.
Expected response to be a redirect to <http://www.example.com/scoreboards/1> but was a redirect to <http://www.example.com/scoreboards/941832920>.
I am not sure exactly what this means. As I mentioned, I have the ID set in the fixtures. I even manually set the id to '941832920'. It still gave me an error. Not sure why its doing that.
For your 2nd test, I don't think you can set an id, it gets assigned by the database when the record is saved. A better way to check whether you get directed to the correct scoreboard path would be the preferred format: assert_redirected_to scoreboard_path(assigns(:scoreboard)).

How do I test a file upload in rails?

I have a controller which is responsible for accepting JSON files and then processing the JSON files to do some user maintenance for our application. In user testing the file upload and processing works, but of course I would like to automate the process of testing the user maintenance in our testing. How can I upload a file to a controller in the functional testing framework?
Searched for this question and could not find it, or its answer on Stack Overflow, but found it elsewhere, so I'm asking to make it available on SO.
The rails framework has a function fixture_file_upload (Rails 2 Rails 3, Rails 5), which will search your fixtures directory for the file specified and will make it available as a test file for the controller in functional testing. To use it:
1) Put your file to be uploaded in the test in your fixtures/files subdirectory for testing.
2) In your unit test you can get your testing file by calling fixture_file_upload('path','mime-type').
e.g.:
bulk_json = fixture_file_upload('files/bulk_bookmark.json','application/json')
3) call the post method to hit the controller action you want, passing the object returned by fixture_file_upload as the parameter for the upload.
e.g.:
post :bookmark, :bulkfile => bulk_json
Or in Rails 5: post :bookmark, params: {bulkfile: bulk_json}
This will run through the simulated post process using a Tempfile copy of the file in your fixtures directory and then return to your unit test so you can start examining the results of the post.
Mori's answer is correct, except that in Rails 3 instead of "ActionController::TestUploadedFile.new" you have to use "Rack::Test::UploadedFile.new".
The file object that is created can then be used as a parameter value in Rspec or TestUnit tests.
test "image upload" do
test_image = path-to-fixtures-image + "/Test.jpg"
file = Rack::Test::UploadedFile.new(test_image, "image/jpeg")
post "/create", :user => { :avatar => file }
# assert desired results
post "/create", :user => { :avatar => file }
assert_response 201
assert_response :success
end
I think it's better to use the new ActionDispatch::Http::UploadedFile this way:
uploaded_file = ActionDispatch::Http::UploadedFile.new({
:tempfile => File.new(Rails.root.join("test/fixtures/files/test.jpg"))
})
assert model.valid?
This way you can use the same methods you are using in your validations (as for example tempfile).
From The Rspec Book, B13.0:
Rails’ provides an ActionController::TestUploadedFile class which can be used to represent an uploaded file in the params hash of a controller spec, like this:
describe UsersController, "POST create" do
after do
# if files are stored on the file system
# be sure to clean them up
end
it "should be able to upload a user's avatar image" do
image = fixture_path + "/test_avatar.png"
file = ActionController::TestUploadedFile.new image, "image/png"
post :create, :user => { :avatar => file }
User.last.avatar.original_filename.should == "test_avatar.png"
end
end
This spec would require that you have a test_avatar.png image in the spec/fixtures directory. It would take that file, upload it to the controller,
and the controller would create and save a real User model.
You want to use fixtures_file_upload. You will put your test file in a subdirectory of the fixtures directory and then pass in the path to fixtures_file_upload. Here is an example of code, using fixture file upload
If you are using default rails test with factory girl. Fine below code.
factory :image_100_100 do
image File.new(File.join(::Rails.root.to_s, "/test/images", "100_100.jpg"))
end
Note: you will have to keep an dummy image in /test/images/100_100.jpg.
It works perfectly.
Cheers!
if you are getting the file in your controller with the following
json_file = params[:json_file]
FileUtils.mv(json_file.tempfile, File.expand_path('.')+'/tmp/newfile.json')
then try the following in your specs:
json_file = mock('JsonFile')
json_file.should_receive(:tempfile).and_return("files/bulk_bookmark.json")
post 'import', :json_file => json_file
response.should be_success
This will make the fake method to 'tempfile' method, which will return the path to the loaded file.

Resources