Contexts in rspec - ruby-on-rails

I have a basic doubt.
If the rspec file contains many contexts:
describe Name do
context "number1" do
.......
.......
end
context "number 2" do
.......
.......
end
context "number 3" do
.......
.......
end
How should the functions from each of the contexts be described in the .rb file? Should they be in the same class or different class? Is there any book I can read to improve my knowledge about this?

The structure I use when defining rspec files (based on reading I've done on rspec) is that you use describes to describe specific functions, and context to talk about a specific context of state and/or path through the function.
Example class:
class MyClass
def self.my_class_method(bool)
if bool == true
return "Yes"
else
return "No"
end
end
def my_instance_method
today = Date.today
if today.month == 2 and today.day == 14
puts "Valentine's Day"
else
puts "Other"
end
end
end
As you can see, I've defined a class method and an instance method that do really silly and random functions. But the point is this: the class method will do something different based on the argument, and the instance method will do something different based on some outside factor: you need to test all these, and these are different contexts. But we will describe the functions in the rspec file.
Rspec file:
describe MyClass do
describe ".my_class_method" do
context "with a 'true' argument" do
it "returns 'Yes'." do
MyClass.my_class_method(true).should eq "Yes"
end
end
context "with a 'false' argument" do
it "returns 'No'." do
MyClass.my_class_method(false).should eq "No"
end
end
end
describe "#my_instance_method" do
context "on Feb 14" do
it "returns 'Valentine's Day'." do
Date.stub(:today) { Date.new(2012,2,14) }
MyClass.new.my_instance_method.should eq "Valentine's Day"
end
end
context "on a day that isn't Feb 14" do
it "returns 'Other'." do
Date.stub(:today) { Date.new(2012,2,15) }
MyClass.new.my_instance_method.should eq "Other"
end
end
end
end
So you can see the describe is for saying what method you're describing, and matches up with the name of a method in your class. The context is used to evaluate different conditions the method can be called in, or different states that affect the way the method works.
Hope this helps!

Related

How to pass variables with the same name in rspec's shared_examples?

Let's imagine I have some tests and want to use shared examples, e.g.:
RSpec.shared_examples "shared examples" do
let(:x) { "" }
it "should be equal to 5" do
expect(x).to eq(5)
end
end
and then use it like:
describe "my tests" do
let(:x) { 5 }
it_behaves_like "shared examples" do
let(:x) { x }
end
end
I know that I can do that implicitly, without passing let(:x) { x } to the child block and everything will work. But what I'm trying to achieve - is to add more clarity to my tests.
So the question is: how to pass a variable (override if you want) with the same name without falling into maximum call stack error to a child shared examples block?
Please let me know if my approach is not right in general.
You just need to define the variable inside blocks, let is lazy-evaluated, so it is not evaluated until the first time
the method it defines is invoked, but they need to be inside blocks like context, something like this:
RSpec.describes "shared examples" do
context 'when x has value' do
let(:x) { 5 }
it "should be equal to 5" do
expect(x).to eq(5)
end
end
context 'when x is empty' do
let(:x) { '' }
it "should be empty" do
expect(x).to eq('')
end
end
end

How to test an if-condition not been excuted?

I'm new to Rspec. I have a code like this:
if #context.persona == :system && #context.scopes&.include?(SEARCH_SCOPE)
return <something>
end
I want to write a unit test to confirm the #context.scopes&.include?(SEARCH_SCOPE) is not being executed when #context.persona is not :system. Here is what I wrote:
context 'when persona is system' do
let(:persona) { :system }
it 'checks the scope' do
allow(context).to receive(:scopes)
expect(context).to have_received(:scopes)
end
end
context 'when persona is not system' do
let(:persona) { :user }
it 'checks the scope' do
allow(context).to receive(:scopes)
expect(context).not_to have_received(:scopes)
end
end
The second test passed, but the first test failed with:
Failure/Error: expect(context).to have_received(:scopes)
(Double (anonymous)).scopes(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Could someone help me? I googled it before but didn't see anything helpful. I'm sorry if it is duplicated.
Not a direct answer to your question, but you are falling into the pit of testing implementation, instead of behaviour. Don't do that.
Your test shouldn't care about this:
expect(context).not_to have_received(:scopes)
Instead, your test should only be doing something like this:
context 'when persona is system and scopes includes SEARCH_SCOPE' do
let(:persona) { :system }
let(:scopes) { ... }
it 'returns <something>' do
expect(the_method_being_invoked).to eq(<something>)
end
end
context 'when persona is not system' do
let(:persona) { :user }
let(:scopes) { ... }
it 'returns <something-else>' do
expect(the_method_being_invoked).to eq(<something-else>)
end
end
context 'when scopes is empty' do
let(:persona) { :user }
let(:scopes) { nil }
it 'returns <something-else>' do
expect(the_method_being_invoked).to eq(<something-else>)
end
end
Why? Because when you refactor code, and the implementation changes, you don't want specs to start failing unless the behaviour has also changed.
You should usually even be able to write the test before writing the method -- therefore having no knowledge of its implementation details.

RSpec: How to pass a "let" variable as a parameter to shared examples

The relevant fragment of spec looks like that:
let(:one_place) { create(:place) }
let(:other_place) { create(:place) }
let(:data) { "saved data" }
shared_examples "saves data to right place" do |right_place|
it { expect(right_place.data).to eq data }
end
context "when something it saves to one place" do
it_behaves_like "saves data to right place", one_place
end
context "when whatever it saves to other place" do
it_behaves_like "saves data to right place", other_place
end
And it would works perfectly with constant parameters, but in this case I receive an error:
one_place is not available on an example group (e.g. a describe or context block). It is only available from within individualexamples (e.g. it blocks) or from constructs that run in the scope of an example (e.g. before, let, etc).
How to pass a lazily created variable to shared examples in such a case?
From the docs, I think you need to put your let statement in a block passed to it_behaves_like:
let(:data) { "saved data" }
shared_examples "saves data to right place" do
it { expect(right_place.data).to eq data }
end
context "when something it saves to one place" do
it_behaves_like "saves data to right place" do
let(:right_place) { create(:place) }
end
end
context "when whatever it saves to other place" do
it_behaves_like "saves data to right place" do
let(:right_place) { create(:place) }
end
end
I'd point out that what you're trying to accomplish is unfortunately not possible. It would be desirable because it makes the usage of such variables explicit. The mentioned workaround (define let where you use it_behaves_like) works but I find shared examples written like that to be confusing.
I use a shared example to make required variables explicit in shared examples:
RSpec.shared_examples "requires variables" do |variable_names|
it "(shared example requires `#{variable_names}` to be set)" do
variable_names = variable_names.kind_of?(Array) ? variable_names : [variable_names]
temp_config = RSpec::Expectations.configuration.on_potential_false_positives
RSpec::Expectations.configuration.on_potential_false_positives = :nothing
variable_names.each do |variable_name|
expect { send(variable_name) }.not_to(
raise_error(NameError), "The following variables must be set to use this shared example: #{variable_names}"
)
end
RSpec::Expectations.configuration.on_potential_false_positives = temp_config
end
end
Use it like this:
RSpec.shared_examples "saves data to right place" do
include_examples "requires variables", :right_place
# ...
end
context "..." do
it_behaves_like "saves data to right place" do
let(:right_place) { "" }
end
end
You can use let variables as below too:
shared_examples 'saves data to right place' do
it { expect(right_place.data).to eq data }
end
context 'when something it saves to one place' do
let(:right_place) { create(:place) }
it_behaves_like 'saves data to right place'
end
context 'when whatever it saves to other place' do
let(:right_place) { create(:place) }
it_behaves_like 'saves data to right place'
end
Note: Use Double quotes only in case of interpolation.
You can make your spec DRY as below :
%w(one other).each do |location|
let(:right_place) { location == one ? create(:place) : create(:place, :other) }
context "when it saves to #{location} place" do
it_behaves_like 'saves data to right place'
end
end

Test if a method is called inside a module method

I have the following in my module:
module SimilarityMachine
...
def answers_similarity(answer_1, answer_2)
if answer_1.compilation_error? && answer_2.compilation_error?
return compiler_output_similarity(answer_1, answer_2)
elsif answer_1.compilation_error? || answer_2.compilation_error?
return source_code_similarity(answer_1, answer_2)
else
content_sim = source_code_similarity(answer_1, answer_2)
test_cases_sim = test_cases_output_similarity(answer_1, answer_2)
answers_formula(content_sim, test_cases_sim)
end
end
...
end
I would like to test these "if conditions", to ensure that the right methods are called (all these methods are from SimilarityMachine module). To do that, I have:
describe SimilarityMachine do
describe '#answers_similarity' do
subject { answers_similarity(answer_1, answer_2) }
let(:answer_1) { create(:answer, :invalid_content) }
context "when both answers have compilation error" do
let(:answer_2) { create(:answer, :invalid_content) }
it "calls compiler_output_similarity method" do
expect(described_class).to receive(:compiler_output_similarity)
subject
end
end
end
With both answers created I go to the right if (the first, and I'm sure of that because I tested before). However, my result is:
1) SimilarityMachine#answers_similarity when both answers have compilation error calls compiler_output_similarity method
Failure/Error: expect(described_class).to receive(:compiler_output_similarity)
(SimilarityMachine).compiler_output_similarity(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
What am I doing wrong?
I would check out Testing modules in rspec other questions related to testing modules.
I'm not completely clear on this, but in general, modules don't receive method calls. They are collections of methods that have to be "mixed in" through the extend method and the like.
Here's an example how to test a module method in isolation, taken from https://semaphoreci.com/community/tutorials/testing-mixins-in-isolation-with-minitest-and-rspec:
describe FastCar
before(:each) do
#test_obj = Object.new
#test_obj.extend(Speedable)
end
it "reports the speed" do
expect(#test_obj.speed).to eq "This car runs super fast!"
end
end

FactoryGirl sequence not incrementing

I'm using FactoryGirl to create instances of a date dimension model for a Rails-related gem. My factory looks like this:
FactoryGirl.define do
sequence :next_day do |n|
Date.new(2000,12,31) + n.days
end
factory :date_dimension do
the_date = FactoryGirl.generate(:next_day)
date {the_date.to_s}
calendar_year {the_date.strftime("%Y")}
(...other attributes created similarly to calendar_year)
end
end
Out of frustration I actually built a little test to show what's not working:
describe "working date factories" do
before(:all) do
#date_dimension = FactoryGirl.create(:date_dimension)
#jan_two = FactoryGirl.create(:date_dimension)
end
describe "sequence incrementing" do
it "returns a date dimension object ok" do
#date_dimension.date.should == "2001-01-01"
end
it "returns the next date in the sequence" do
#jan_two.date.should == "2001-01-02"
end
end
end
When I run that test, I get:
working date factories
sequence incrementing
returns a date dimension object ok
returns the next date in the sequence (FAILED - 1)
Failures:
1) working date factories sequence incrementing returns the next date in the sequence
Failure/Error: #jan_two.date.should == "2001-01-02"
expected: "2001-01-02"
got: "2001-01-01" (using ==)
I've read a bunch of other questions related to sequences, but it doesn't seem that I'm making the mistakes identified therein. It's a different (likely dumber) mistake. What is it?
I finally found an approach that works, and is probably a little better anyway. I still don't understand why the code above doesn't work - if someone can explain that to me (maybe with a reference to a doc or part of the source code), I'll go ahead and accept that answer - this post is just for those who follow. Here's what worked:
FactoryGirl.define do
factory :date_dimension do
sequence(:date) { |n| (Date.new(2000,12,31) + n.days).to_s }
calendar_year { Date.parse(date).strftime("%Y") }
day_of_week { Date.parse(date).strftime("%A") }
end
end
The code above passes this test:
describe "working date factories" do
before(:all) do
#date_dimension = FactoryGirl.create(:date_dimension)
#jan_two = FactoryGirl.create(:date_dimension)
end
describe "sequences" do
it "returns the proper first date in the sequence" do
#date_dimension.date.should == "2001-01-01"
#date_dimension.calendar_year.should == "2001"
#date_dimension.day_of_week.should == "Monday"
end
it "returns the next date in the sequence" do
#jan_two.date.should == "2001-01-02"
#jan_two.calendar_year.should == "2001"
#jan_two.day_of_week.should == "Tuesday"
end
end
end

Resources