handle repetitive model creates - ruby-on-rails

I am using Rspec in Rails. I am currently testing one of my models. This specific model is seeded into the database with 5 records. Each record has a name attribute with a seeded value. For example:
def self.seed
find_or_create_by(name: 'Text')
find_or_create_by(name: 'Number')
find_or_create_by(name: 'Date')
find_or_create_by(name: 'Time')
find_or_create_by(name: 'Boolean')
end
I want to test the creation of each one in Rspec. And currently I am doing this:
describe FieldType, type: :model do
context "when :name is text" do
let(:field_type) { FieldType.create(name: "Text") }
it { expect(field_type).to be_persisted }
it { expect(field_type.name).to eq("Text")}
it { expect(field_type.type_from_field).to be(String) }
end
context "when :name is number" do
let(:field_type) { FieldType.create(name: "Number") }
it { expect(field_type).to be_persisted }
it { expect(field_type.name).to eq("Number")}
it { expect(field_type.type_from_field).to be(Integer) }
end
...
Is this the right way to test this behavior? Or is there a more efficient way to do it?

It looks like the System Under Test here is the method .seed; so, maybe something like this:
describe FieldType do
describe '.seed' do
before do
described_class.seed
end
it 'creates the expected records' do
expect(described_class.pluck(:name)).to match_array(['Text', 'Number', 'Date', 'Time', 'Boolean'])
end
end
end
Also please check Better Specs for some recommendations and rubocop-rspec for automatically checking your code against some RSpec best practices.

Related

looping over inside a context in rspec doesn't set the let variable correctly

MY_HASH = {
user_id: [:email, :first_name],
email: [:last_name]
}
context "when object's single attribute changed" do
let(:object) { double("my_object", :changed? => true) }
before do
allow(object).to receive("#{attribute}_changed?").and_return(true)
end
after do
allow(object).to receive("#{attribute}_changed?").and_return(false)
end
MY_HASH.each do |attr, dependent_attrs|
let(:attribute) { attr }
it "should have all dependent attributes in right order for defaulting attribute" do
expect(subject.send(:my_method)).to eq(dependent_attrs)
end
end
end
here attribute is always evaluated to email. I want to iterate over each attribute one by one.
Can anyone help me understand what's going wrong here?
Thanks,
It's because you're redefining attribute each loop:
MY_HASH.each do |attr, dependent_attrs|
let(:attribute) { attr }
To fix this, you could introduce a new context/describe block for each iteration:
MY_HASH.each do |attr, dependent_attrs|
describe("#{attr}") do
let(:attribute) { attr }
it "should have all dependent attributes ..." do
# content of test here
end
end
end

why is my rails scope query on jsonb not working?

I have a jsonb column with default {}, added the key "home_page":"1" (update_attribute and save...).
I added a scope to the model -
scope :home_page, -> { where("my_column ->> 'home_page' = ?", "1") }
no matter what I do I'm always getting an empty result.
help :(
rails - 5.2.2,
ruby - 2.5,
db - PostgreSQL 10.3
Your scope is working.
class Thing < ApplicationRecord
scope :home_page, -> { where("my_column ->> 'home_page' = ?", "1") }
end
As shown by this passing spec:
require 'rails_helper'
RSpec.describe Thing, type: :model do
after(:each) { Thing.destroy_all }
describe '.home_page' do
it "includes records with home_page: 1" do
thing = Thing.create(my_column: { home_page: 1 })
expect(Thing.home_page).to include thing
end
it "includes records with home_page: '1'" do
thing = Thing.create(my_column: { home_page: '1' })
expect(Thing.home_page).to include thing
end
it "does not return records that do not match the criteria" do
Thing.create(my_column: { home_page: 0 })
Thing.create(my_column: { xhome_page: 1 })
expect(Thing.home_page).to be_empty
end
end
end
Rather the error is in your test methodology or the setup. For example you can try calling .save! to see if there are any validation errors.

Passing variable between multiple contexts with Rspec

I'm writing some tests using FactoryGirl and Rspec.
spec/factories/students.rb:
FactoryGirl.define do
factory :student do
end
factory :student_with_profile_and_identity, class: 'Student' do
after(:create) do |student|
create(:profile, profileable: student)
create(:student_identity, student: student)
end
end
end
spec/factories/profiles.rb:
FactoryGirl.define do
factory :profile do
birthday { Faker::Date.birthday(15, 150) }
sequence(:email) { |i| "profile_#{i}#email.com" }
first_name { Faker::Name.first_name }
last_name { Faker::Name.first_name }
password { Faker::Internet.password(6, 72, true, true) }
end
end
spec/factories/student_identities.rb:
FactoryGirl.define do
factory :student_identity do
provider { ['facebook.com', 'google.com', 'twitter.com'].sample }
uid { Faker::Number.number(10) }
end
end
spec/requests/authorizations_spec.rb:
require 'rails_helper'
RSpec.describe 'Authorizations', type: :request do
describe 'POST /v1/authorizations/sign_in' do
let!(:student) { create(:student_with_profile_and_identity) }
context 'when the request is valid' do
subject do
post '/v1/authorizations/sign_in',
params: credentials
end
context "user signs up via social network" do
let(:credentials) do
{
authorization: {
student: {
profile_attributes: {
email: student.profile.email
},
student_identities_attributes: {
provider: student.student_identities[0].provider,
uid: student.student_identities[0].uid
}
}
}
}
end
it 'returns an authentication token' do
subject
p "1 student.profile.inspect #{student.profile.inspect}"
expect(json['token']).to(be_present)
end
end
context 'when the user has already an account' do
let(:credentials) do
{
authorization: {
email: student.profile.email,
password: student.profile.password
}
}
end
it 'returns an authentication token' do
p "2 student.profile.inspect #{student.profile.inspect}"
subject
expect(json['token']).to(be_present)
end
end
end
end
end
Almost all tests are passing... the problem is that:
It's creating a new student in every context. I'd expect the let!(:student) { ... } to be something like "singleton", in other words, once it's created/defined here let!(:student) { create(:student_with_profile_and_identity) } it won't be called anymore.
Ex: the logs are like this:
"1 student.profile.inspect #<Profile id: 1, email: \"profile_1#email.com\", profileable_type: \"Student\", profileable_id: 1>"
"2 student.profile.inspect #<Profile id: 2, email: \"profile_2#email.com\", profileable_type: \"Student\", profileable_id: 2>"
While I'd expect the instances to be the same.
Am I missing something?
In RSpec, let and let! are the same thing, except that let is lazy and let! is eager:
Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.
If you want something to persist through all examples, you can use a before hook...before(:context) sounds like it might be what you're wanting. You might be able to setup a helper method that memoizes in a before block, to avoid having to use an instance variable everywhere (per this comment):
def student
#student ||= create(:student_with_profile_and_identity)
end
before(:context) do
student # force student creation
end

Rails: callback to prevent deletion

I've got a before_destroy callback that looks like this:
class Component < ActiveRecord::Base
has_many :documents, through: :publications
def document_check
if documents.exists?
errors[:documents] << 'cannot exist'
return true
else
return false
end
end
The test looks like this:
describe '#document_check' do
let(:document) { create(:document) }
let(:component) { create(:component) }
context 'with documents' do
before do
document.components << component
end
specify { expect(component.errors).to include(:document, 'cannot exist') }
specify { expect(component.document_check).to eq true }
end
context 'without documents' do
before do
document.components = []
end
specify { expect(component.document_check).to eq false }
end
end
I want it to raise the error if a component is in a document, but I can't seem to be able to write it correctly. The second test passes, the first doesn't:
Diff:
## -1,2 +1,2 ##
-[:document, "cannot exist"]
+[]
What am I doing wrong?
How is document_check being invoked? If manually (as you're 2nd tests seem to suggest) then you also need to invoke it for the first specify.
That is:
specify { component.document_check; expect(component.errors).to include(:document, 'cannot exist') }
That's horrible syntax, but you need to invoke the method before you can check the errors on it.
Here's the callback:
def document_check
return unless documents.present?
errors.add(:article, 'in use cannot be deleted')
false
end
And here's the passing test for it.
describe '#document_check' do
let(:subject) { create(:component) }
let(:document) { create(:document) }
let(:count) { Component.size }
before do
document.components << subject
subject.send :document_check
end
context 'with documents raises error' do
specify do
expect(subject.errors[:article]).to be_present
end
end
context 'with documents raises correct error' do
specify do
expect(subject.errors[:article]).to include(
'in use cannot be deleted')
end
end
context 'with documents prevents deletion' do
specify do
expect { subject.destroy }.to_not change(Component, :count)
end
end
end
Took ages but it's worth it.

RSpec, how to test initialize inside a module

I have this module:
module TicketsPresenters
class ShowPresenter
def initialize(ticket_or_id = nil)
unless ticket_or_id.nil?
if ticket_or_id.is_a?(Ticket)
#ticket = ticket_or_id
else
#ticket = Ticket.find(ticket_or_id)
end
end
end
end
I'd like to test if the initialize() method sets up the object properly, when I pass an integer or directly the object instance.
There's probably a dozen ways to answer this, but I'll give you the RSpec format that I prefer.
The following assumes you have a reader method for ticket (ie attr_reader :ticket) in your ShowPresenter class. It also assumes you are creating your Ticket object with valid parameters so it gets saved.
describe TicketsPresenters::ShowPresenter do
context '#initialize' do
let!(:ticket) { Ticket.create!(...) }
context 'with an id' do
subject { TicketsPresenters::ShowPresenter.new(ticket.id) }
its(:ticket) { should == ticket }
end
context 'with a Ticket object' do
subject { TicketsPresenters::ShowPresenter.new(ticket) }
its(:ticket) { should == ticket }
end
context 'with nothing' do
subject { TicketsPresenters::ShowPresenter.new }
its(:ticket) { should be_nil }
end
end
end
Note: I'm a fan of FactoryGirl so I would personally prefer to use Factory.create(:ticket) over Ticket.create!(...) since it allows you define a valid Ticket object in one place and you don't need to update it in all your tests if that definition changes.
Another testing position that people take is to not use database persistance at all. This is probably not a concept I would suggest to people new to Ruby or RSpec since it is a little harder to explain and would require more OOP knowledge. The upside is that it removes the database dependency and tests are faster and more isolated.
describe TicketsPresenters::ShowPresenter do
context '#initialize' do
let(:ticket) { mock(:ticket, id: 1) }
before do
ticket.stub(:is_a?).with(Ticket) { true }
Ticket.stub(:find).with(ticket.id) { ticket }
end
context 'with an id' do
subject { TicketsPresenters::ShowPresenter.new(ticket.id) }
its(:ticket) { should == ticket }
end
context 'with a Ticket object' do
subject { TicketsPresenters::ShowPresenter.new(ticket) }
its(:ticket) { should == ticket }
end
context 'with nothing' do
subject { TicketsPresenters::ShowPresenter.new }
its(:ticket) { should be_nil }
end
end
end

Resources