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
Related
I have a class, that in one situation should call :my_method, but in another situation must not call method :my_method. I would like to test both cases. Also, I would like the test to document the cases when :my_method should not be called.
Using any_instance is generally discouraged, so I would be happy to learn a nice way to replace it.
This code snippet is a reduced example on what I kind of test I would like to write.
class TestSubject
def call
call_me
end
def call_me; end
def never_mind; end
end
require 'rspec'
spec = RSpec.describe 'TestSubject' do
describe '#call' do
it 'calls #call_me' do
expect_any_instance_of(TestSubject).to receive(:call_me)
TestSubject.new.call
end
it 'does not call #never_mind' do
expect_any_instance_of(TestSubject).not_to receive(:never_mind)
TestSubject.new.call
end
end
end
spec.run # => true
It works, but uses expect_any_instance_of method, which is not recommended.
How to replace it?
I'll do somehting like that
describe TestSubject do
describe '#call' do
it 'does not call #something' do
subject = TestSubject.new
allow(subject).to receive(:something)
subject.call
expect(subject).not_to have_received(:something)
end
end
end
Hope this helped !
This is how I normally unit-test. I updated the code to support other possible questions you (or other readers) may have in the future.
class TestSubject
def call
some_call_me_value = call_me
call_you(some_call_me_value)
end
def call_me; end
def call_you(x); end
def never_mind; end
class << self
def some_class_method_a; end
def some_class_method_b(x, y); end
end
end
require 'rspec'
spec = RSpec.describe TestSubject do
context 'instance methods' do
let(:test_subject) { TestSubject.new }
describe '#call' do
let(:args) { nil }
let(:mocked_call_me_return_value) { 'somecallmevalue' }
subject { test_subject.call(*args) }
before do
allow(test_subject).to receive(:call_me) do
mocked_call_me_return_value
end
end
it 'calls #call_me' do
expect(test_subject).to receive(:call_me).once
subject
end
it 'calls #call_you with call_me value as the argument' do
expect(test_subject).to receive(:call_you).once.with(mocked_call_me_return_value)
subject
end
it 'does not call #never_mind' do
expect(test_subject).to_not receive(:never_mind)
subject
end
it 'calls in order' do
expect(test_subject).to receive(:call_me).once.ordered
expect(test_subject).to receive(:call_you).once.ordered
subject
end
end
describe '#call_me' do
let(:args) { nil }
subject { test_subject.call_me(*args) }
# it ...
end
describe '#call_you' do
let(:args) { nil }
subject { test_subject.call_you(*args) }
shared_examples_for 'shared #call_you behaviours' do
it 'calls your phone number'
it 'creates a Conversation record'
end
# just an example of argument-dependent behaviour spec
context 'when argument is true' do
let(:args) { [true] }
it 'does something magical'
it_behaves_like 'shared #call_you behaviours'
end
# just an example of argument-dependent behaviour spec
context 'when argument is false' do
let(:args) { [false] }
it 'does something explosive'
it_behaves_like 'shared #call_you behaviours'
end
end
end
context 'class methods' do
let(:args) { nil }
describe '#some_class_method_a' do
let(:args) { nil }
subject { TestSubject.some_class_method_a(*args) }
# it ...
end
describe '#some_class_method_b' do
let(:args) { [1, 2] }
subject { TestSubject.some_class_method_b(*args) }
# it ...
end
end
end
spec.run # => true
Do not test if some method was called or wasn't.
This will tight your tests to the implementation details and will force you to change tests every time you refactor(change implementation details without changing the behaviour) your class under test.
Instead test against return value or changed application state.
It is difficult come up with the example, you didn't provide enough context about the class under the test.
class CreateEntity
def initialize(name)
#name = name
end
def call
if company_name?(#name)
create_company
else
create_person
end
end
def create_person
Person.create!(:name => #name)
end
def create_company
Company.create!(:name => #name)
end
end
# tests
RSpec.describe CreateEntity do
let(:create) { CreateEntity.new(name).call }
describe '#call' do
context 'when person name is given' do
let(:name) { 'Firstname Lastname' }
it 'creates a person' do
expect { create }.to change { Person.count }.by(1)
end
it 'do not create a company' do
expect { create }.not_to change { Company.count }
end
end
context 'when company name is given' do
let(:name) { 'Name & Sons Ltd' }
it 'creates a company' do
expect { create }.to change { Company.count }.by(1)
end
it 'do not create a person' do
expect { create }.not_to change { Person.count }
end
end
end
end
With tests above I would be able to change how CreateEntity.call method implemented without changing tests as far as behaviour remain same.
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.
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
In rspec 3.2, I have something based on the following pseudocode:
context 'my test context' do
before do
method_that_uses(error_message)
end
subject { post :my_action, params: a_bunch_of_params }
let(:error_message) { 'error' }
it { is_expected.to raise_error(MyException) }
let(:error_message) { 'different error' }
it { is_expected.to redirect_to(a_path) }
let(:error_message) { 'third error' }
it { is_expected.to redirect_to(another_path) }
end
Every example runs with error_message set to third error. I confirmed this by running pry from the before hook as well. How can I get the desired behavior?
That happens because internally, let uses define_method as seen in the source for let. You can create a quick example
class A
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
end
a = A.new
a.create_method(:foo) { puts "bar" }
a.create_method(:foo) { puts "baz" }
a.foo
and run that and you would see that define_method overrides the previous method with the new one. So in your example, you are creating a method and then overriding it's definition twice before you ever get a chance to call it.
You are wanting to run each error_message in it's own context like this:
def method_that_uses(e)
puts "running with: #{e}"
end
context 'my test context' do
before do
method_that_uses(error_message)
end
context 'error' do
let(:error_message) { 'error' }
it { puts "run one" }
end
context 'second error' do
let(:error_message) { 'different error' }
it { puts "run two" }
end
context' third error' do
let(:error_message) { 'third error' }
it { puts "run three" }
end
end
which, when run, outputs
running with: error
run one
.running with: different error
run two
.running with: third error
run three
.
This works because describe and context blocks create a new ExampleGroup (source) and ExampleGroup states
Example group bodies (e.g. describe or context blocks) are evaluated in the context of a new subclass of ExampleGroup.
So, let(:error_message) is now defining those methods on different subclasses.
I am testing a single method in the model. It's called last_photo, I filled in the data of the database and try to return the first element, but I have an error udefined method 'latest'. What could it be? How can I fix it?
Method latest this:
scope :latest, -> { order('created_at DESC') }
def last_photo
#last_photo ||= user_updates.latest.where("photo_front IS NOT NULL and photo_front != ''").first.try(:photo_front)
end
context "instance method" do
let(:user) { create :user }
context "last photo" do
before { create_list(:user_update, 3, user: user) }
let(:user_updates){ UserUpdate.all }
describe "#last_photo" do
subject { user.last_photo }
it { should eq user_updates.latest.first.photo_front }
end
describe "#last_photo_side" do
subject { user.last_photo_side }
it { should eq user_updates.latest.first.photo_side}
end
end
end
Thanks.
I bet UserUpdate.all returns an array. So you cant chain scopes on it.
Replace with:
let(:user_updates){ UserUpdate.scoped }