Rails 6 Rspec throws "ActiveModel::MissingAttributeError" on Carrierwave uploader - carrierwave

I've got a FactoryBot factory that is erroring after upgrading to Rails 6. The error is:
Failure/Error: let(:lodge) { FactoryBot.create(:lodge) }
ActiveModel::MissingAttributeError:
can't write unknown attribute `checklist`
Checklist is an uploader (Carrierwave) on Lodge, mounted via the model, with permitted params for the field and checklist_cache.
I have run db:test:prepare, and the field exists in the test database (Lodge.checklist is a string).
The factory in question:
FactoryBot.define do
factory :lodge do
name { Faker::Name.first_name }
number { Faker::Number.number(digits: 3) }
designator { Faker::Lorem.characters(number: 1) }
combined { "999A" }
status { "active" }
association :council
checklist { Rack::Test::UploadedFile.new(Rails.root.join('spec/support/lodge-checklist.pdf'), 'application/pdf') }
end
end
The error persists whether or not I have the checklist line present.
Ruby 2.6.3; Rails 6.0.0, Carrierwave 2.0.0 (also occuring on master branch)

This apparently was caused by the uploader being in the included block on a concern used by a variety of shared objects. Moving it back to the model alone fixed the problem.

Related

How do I create a rails association for cypress testing?

I am trying to test a form on one particular page on my web app. The problem is that this web page depends on at least three model objects to be in the database for various reasons that I'll elaborate and since I'm new to Cypress for the testing, I'm not exactly sure how to go about this. So here are the problem areas:
describe('Basic SSL Certificate', () => {
context('csr submission', () => {
beforeEach(() => {
cy.request('POST', 'user_session/user_login', { login: 'testuser', password: 'Testing_ssl+1'})
.as('currentUser')
cy.appFactories([
['create', 'certificate_order']
]).as('certificateOrder')
})
it('rejects a numerical ip address for its csr', () => {
cy.visit(`/team/${this.certificateOrder.ssl_account.ssl_slug}/certificateOrders/${this.certificate_order.ref}/edit`);
First of all, the problem I am facing is this simple line of test code here:
cy.visit(`/team/${this.certificateOrder.ssl_account.ssl_slug}/certificateOrders/${this.certificate_order.ref}/edit`);
I need to hit the following url which looks like this /teams/abcd-xyz/certificate_orders/co-ref-1234/edit
Questions: How do I create rails associations with cypress? In my before block, I think I created a certificate order, does that have the associations with it on creation? Do I have to create each model seperately with appFactories and if I do, how do I "link" them together?
I don't see the way to combine ruby and javascript in this code and could use a pointer on setting up the factories. Usually in rspec I would create the models that I need and use them but in cypress I'm not sure how to do this because it doesn't seem to be the right way of doing it with JS. Helpful advice would be appreciated, thank you.
There are a few ways you can setup associations:
Setting the foreign keys
Using transient attributes
Using Nested Attributes
I have examples of all 3 in this post: https://medium.com/nexl-engineering/setting-up-associations-with-cypress-on-rails-and-factorybot-3d681315946f
My preferred way is using transient attributes as it makes is very readable from within Cypress
Example:
FactoryBot.define do
factory :author do
name { 'Taylor' }
end
factory :post do
transient do
author_name { 'Taylor' }
end
title { 'Cypress on Rails is Awesome' }
author { create(:author, name: author_name ) }
end
end
Then in Cypress you can do the following:
// example with overriding the defaults
cy.appFactories([['create', 'post', { title: 'Cypress is cool', author_name: 'James' }]]
// example without overriding
cy.appFactories([['create', 'post']]

Using factory_girl with PaperClip 4.0

Does anyone know the proper way to create PaperClip 4.0 attachments with factory_girl, bypassing any of the PaperClip processing and validation?
I used to just be able to do the following in my factory:
factory :attachment do
supporting_documentation_file_name { 'test.pdf' }
supporting_documentation_content_type { 'application/pdf' }
supporting_documentation_file_size { 1024 }
# ...
end
This would basically trick PaperClip into thinking that there was a valid attachment.
After upgrading from 3.5.3 to 4.0, I now get a validation error:
ActiveRecord::RecordInvalid: Validation failed: Image translation missing: en.activerecord.errors.models.attachment.attributes.supporting_documentation.spoofed_media_type
NOTE: The original discussion for PaperClip 3.X is here: How Do I Use Factory Girl To Generate A Paperclip Attachment?
The issue appears to be caused by line 61 in media_type_spoof_detector.
Paperclip is trying to find the mime type of the "file" you have uploaded. When there isn't one, it's failing validation to protect you from file type spoofing.
I haven't tried this myself, but perhaps your best bet would be to use a real file, and set it using the fixture_file_upload method from ActionDispatch::TestProcess.
factory :attachment do
supporting_documentation { fixture_file_upload 'test.pdf', 'application/pdf' }
# This is to prevent Errno::EMFILE: Too many open files
after_create do |attachment, proxy|
proxy.supporting_documentation.close
end
end
You will need to include ActionDispatch::TestProcess in test_helper.rb
This was first posted here.

How to get a simple test to run with rspec + fabrication?

I'm trying to get a simple test up and running with rspec + fabrication. Unfortunately, not too many decent articles on this.
In spec/model/event_spec.rb
require 'spec_helper'
describe Event do
subject { Fabricate(:event) }
describe "#full_name" do
its(:city) { should == "LA" }
end
end
In spec/fabricators/event_fabricator.rb
Fabricator(:event) do
user { Fabricate(:user) }
# The test works if I uncomment this line:
# user_id 1
city "LA"
description "Best event evar"
end
In spec/fabricators/user_fabricator.rb
Fabricator(:user) do
name 'Foobar'
email { Faker::Internet.email }
end
I keep getting:
1) Event#full_name city
Failure/Error: subject { Fabricate(:event) }
ActiveRecord::RecordInvalid:
Validation failed: User can't be blank
PS if anyone knows of any online articles / tutorials worth a read on getting started with rspec and fabrication. Do let me know
One of the features of Fabricator is that it lazily generates associations, meaning that your User won't get generated unless the user accessor is called on the Event model.
It looks like your Event model has a validation which requires a User to be present. If this is the case, you need to declare your fabricator like this:
Fabricator(:event) do
# This forces the association to be created
user!
city "LA"
description "Best event evar"
end
This ensures that the User model is created along with the Event, which will allow your validations to pass.
see: http://fabricationgem.org/#!defining-fabricators

What's the benefit of Class.new in this Rspec

I am reading through some Rspec written by someone who left the company. I am wondering about this line:
let(:mailer_class) { Class.new(AxeMailer) }
let(:mailer) { mailer_class.new }
describe '#check' do
before do
mailer_class.username 'username'
mailer.from 'tester#example.com'
mailer.subject 'subject'
end
subject { lambda { mailer.send(:check) } }
It is testing this class:
class AxeMailer < AbstractController::Base
def self.controller_path
#controller_path ||= name.sub(/Mailer$/, '').underscore
end
I want to know the difference between this and let(:mailer_class) { AxeMailer }.
I ask this because currently when I run the test, it will complain name is nil. But if I changed it, it will test fine.
I think this issue started after using Rails 3.2, and I think name is inherited from AbstractController::Base.
This is the same in the console (meaning it is not Rspec specific), I can do AxeMailer.name with no error, but if I do Class.new(AxeMailer) there is is the problem.
My questions are:
Is there a reason to use Class.new(AxeMailer) over AxeMailer
Is there a problem if I just change this?
Is there a way not change the spec and make it pass?
I'm guessing it was written this was because of the mailer_class.username 'username' line. If you just used AxeMailer directly, the username setting would be carried over between tests. By creating a new subclass for each test, you can make sure that no state is carried over between them.
I don't know if mailer_class is being used inside the actual spec or not, but this is what I think your setup should look like:
let(:mailer) { AxeMailer.new }
describe '#check' do
before do
AxeMailer.username 'username'
mailer.from 'tester#example.com'
mailer.subject 'subject'
end
subject { lambda { mailer.send(:check) } }
There just doesn't seem to be a need for the anonymous class that was being created. Also, this is just my opinion, but your subject looks a bit odd. Your spec should probably wrap the subject in a lambda if it needs to, but don't do that in your subject.
Regarding the error you were seeing originally, anonymous classes don't have names:
1.9.3-p0 :001 > Class.new.name
=> nil
Some part of ActionMailer::Base must attempt to use the class name for something (logging perhaps) and breaks when it's nil.

Trouble on testing a model method call in a spec file

I am using Ruby on Rails 3.0.10, RSpec 2 and FactoryGirl. I am trying to validate user account data.
In my model I have:
class Users::Account < ActiveRecord::Base
...
def get_account_password_salt(password)
make_user_account_password_salt(password)
end
end
In my spec file I have
it "should have a valid password salt" do
users_account.password_salt.should == Users::Account.get_account_password_salt('123456')
end
When I run the rspec command I get the following error:
Users::Account should have a valid password salt
Failure/Error: users_account.password_salt.should == Users::Account.get_account_password_salt('123456')
NoMethodError:
undefined method `get_account_password_salt' for #<Class:0x0000010729c9f0>
What is the problem and how can I solve that?
I also tryed to use ::Users::Account.get_account_password_salt('123456') (note the :: at the beginning) but it still doesn't work.
SOLUTION
The problem was that I have to run
users_account.get_account_password_salt('123456')
instead of:
Users::Account.get_account_password_salt('123456')

Resources