rspec - "after it" - how to change from should to expect syntax? - ruby

How can I change from should to expect for the it in this code:-
context 'class_name' do
before do
Fabricator(:some_company, :class_name => Company) do
name 'Hashrocket'
end
end
after { Fabrication.clear_definitions }
its(:name) { should == 'Hashrocket' }
it { should be_kind_of(Company) }
end
I can see that the its will probably be:
expect(name.to eq 'Hashrocket')
but what should the it { should be_kind_of(Company) } become given the implicit subject.
Would it be
expect(it).to be_kind_of(Company)
?
I don't have the project set up yet from github (it is large).

There's no reason to change the short-form it blocks to the expect syntax. One of the reasons for the new syntax is to avoid the monkeypatching required to add should to every object. The short-form it blocks already avoid this problem.
However, if you need to access the implicit subject, you can say
expect(subject).to be_kind_of(Company)
Although I prefer to name my subjects explicitly:
subject(:company) { Company.new }
it 'something' do
expect(company).to be_kind_of(Company)
end

Related

In RSpec, how to prepend a before hook in a subcontext that accesses the subject?

Consider the following:
describe MyModel do
context 'updates fields' do
subject { create(:my_model) }
before do
subject.save
subject.reload
end
context 'when changing foo.bar' do
before { subject.foo.bar = 3 }
it { is_expected.to be_multiple_bar }
end
context 'when changing baz.quux' do
before { subject.baz.quux = 3 }
it { is_expected.to be_multiple_quux }
end
end
end
Now, as you may expect, I want the before hook on line 4 to be invoked after the ones on lines 10 and 15.
I've tried 2 things:
I have tried using prepend_before, but that only works when they're defined in the same context, it doesn't allow you to prepend a hook before one that's defined in the supercontext
I have tried using before(:context) on line 10 and 15, and while this should put them in the right order, RSpec doesn't allow me to mutate the subject at that point yet. (And for good reason, I'm not trying to create a shared state here.)
I really don't want to resort to let(:append_before) { proc { #magic here } }, because it's ugly and hacky as hell. Besides, I think what I want is totally reasonable. Right now I copied the two lines over to all subcontexts, which I am not too happy with.
What is a better way to do this?
I am on RSpec 3.7, FactoryGirl 4.8.0 and Ruby 2.3.1
I don't know what your factory looks like, but instead to creating and persisting my_model, modifying, saving and reloading it, you should create it only once. This will also speed up your specs.
You could write something like this:
describe MyModel do
context 'updates fields' do
subject { create(:my_model, foo: {bar: bar}, baz: {quux: quux}) }
context 'when changing foo.bar' do
let(:bar) { 3 }
it { is_expected.to be_multiple_bar }
end
context 'when changing baz.quux' do
let(:quux) { 3 }
it { is_expected.to be_multiple_quux }
end
end
end
Lazy evaluation of both let and subject makes sure you all your parameters are set correctly depending on the context. In case you need/want to extend your factory to support that, check out http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md
I hope that makes sense.

First attempt in Ruby Rspec

I am new to Ruby and Rspec, and so I happened to found this bit of code:
Here is my Specification:
RSpec.describe Surveyor::Answer, '03: Answer validations' do
context "for a free text question" do
let(:question) { double(Surveyor::Question, type: 'free_text') }
# NOTE: The rating validations should not apply for 'free_text' questions.
subject { described_class.new(question: question, value: 'anything') }
it { should be_valid }
end
Here is my Class:
module Surveyor
class Answer
def initialize(question_answer)
#question = question_answer[:question]
#answer = question_answer[:value]
end
def question_type
# I want to check what is the type of question here.
# 'free_text' or 'rating'
# if free_text
# print question type
# else
# do something
end
end
My question is how can I print(puts) the type of question (free_text/rating) in Answer class?
When I tried using print question_answer[:question]it only gave me #<Double Surveyor::Question>
So I could not use question_answer[:question][:type]
You can access the type in the constructor simply: question_answer[:question].type, or later in object level methods: #question.type.
You can't access it like question_answer[:question][:type] because the double method in the test creates a classic like object rather than a hash.
A tip: when a method accepts parameters as a single hash you can simply name that as options or params but if you have only 3-4 params, you can use separate variables for params instead of a hash

How do I convert from "should" to "expect" convention?

I've just started working with rspec, and I use expect instead of should convention.
How can I transform this test example from CanCan from should to expect?:
require "cancan/matchers"
# ...
describe "User" do
describe "abilities" do
subject { ability }
let(:ability){ Ability.new(user) }
let(:user){ nil }
context "when is an account manager" do
let(:user){ Factory(:accounts_manager) }
it{ should be_able_to(:manage, Account.new) }
end
end
end
You actually don't have to replace this instance of should, per Using implicit `subject` with `expect` in RSpec-2.11, but if you want to, you'd have to give up the one-liner approach and use:
it "should be able to manage a new account" do
expect(ability).to be_able_to(:manage, Account.new)
end
in place of the current it clause. As an aside, there looks to be some extraneous code in this test.

How to share data between implementation and description of a spec?

I wonder if there's any good way to reuse data between implementation and description of a spec... More particularly, I'd like to be able do something like the following:
describe "#some_method" do
let(:arg1) { "Sample String 1" }
let(:arg2) { "Sample String 2" }
context "with '#{arg1}', its result" do
specify { some_method(arg1).should == 1 }
end
context "with '#{arg2}', its result" do
specify { some_method(arg2).should == 2 }
end
end
Of course, this code won't work - arg1 and arg2 are not accessible outside of spec bodies.
Is it possible to achieve the similar result without using global variables or external classes?
Update:
I'm interested in the output of the spec. Something like this:
#some_method
with 'Sample String 1' its result
should == 1
with 'Sample String 2' its result
should == 2
The answer is that you don't use dynamic descriptions. The RSpec way to do this would be
describe "#some_method" do
it "extracts the number correctly" do
some_method("Sample String 1").should == 1
some_method("Sample String 2").should == 2
end
end
It is no problem to hard-code test data in your specs. If you want more complete output, you can use a custom matcher
require 'rspec'
class Test
def some_method(str)
str[/[0-9]+/].to_i
end
end
RSpec::Matchers.define :return_value_for_argument do |result, arg|
match do |actual|
actual.call(arg) == result
end
description do
"return #{result.inspect} for argument #{arg.inspect}"
end
end
describe Test do
let(:test) { Test.new }
describe "#some_method" do
subject { test.method(:some_method) }
it { should return_value_for_argument 1, "str 1" }
end
end
When doing API testing, I find it incredibly useful to be able to see the path, params, and response of each test. I have used the very useful tips given by Jeff Nyman to store things in the example.metatadata[:thing_i_want_to_store_like_url] of each test and with a custom formatter, print it out.
So my tests output look something like this:
that jonathan does not know it exists
:path : /user/20
:params: {}
=> response: {"error"=>{"message"=>"error", "code"=>404}}
that jonathan cannot edit
:path : /user/20/update
:params: {:name=>"evil_name"}
=> response: {"error"=>{"message"=>"error", "code"=>404}}
It's not appropriate to cite specific arguments in your descriptions. Your descriptions should provide a human-readable description of the desired behavior, without reference to specific arguments in most cases.

rspec `its` syntax with dynamic conditions

I've been really loving using contexts, subjects and its with rspec to really clean up my test code. Typical example:
context "as a user" do
subject{ Factory :user }
its(:name){ should == "Bob" }
end
What I can't figure out though is how I could make this condition dynamic (ie. based on other objects). its appears to instance eval the attribute within the block so I lose access to everything around it. I'd love to do something like:
its(:name){ should == subject.contact.name }
But I can't see any way of achieving this. Does anyone know if there is some a method proxied through to this instance eval that gives access to the origin object? Or if there's any other way I can use methods outside the scope of the instance of the attribute that I'm checking?
additional info
It seems as if subject within the its block gets changed to the actual attribute (name in this case)
Interestingly, I have access to any of my let methods, but again, I don't have access to my original subject in question.
You are almost answering yourself, use a let assignment before you set the subject. Then you can reference it everywhere:
context "as a user" do
let(:user) { Factory(:user) }
subject { user }
its(:name) { should == user.contact.name }
end
I'm a context, subject, its lover too !
Not sure if there's a way to do exactly what you're asking for since the "subject" within the block becomes the return value of User#name.
Instead, I've used the let method along with the following spec style to write this kind of test:
describe User do
describe '#name' do
let(:contact) { Factory(:contact, name: 'Bob') }
let(:user) { Factory(:user, contact: contact) }
subject { user.name }
it { should == 'Bob' }
end
end
This of course makes some assumptions about what your contact represents (here it's an association or similar). You may also choose to stub the return value of User#contact instead of relying on FactoryGirl to set up a "real" association.
Regardless of the choices you make on those fronts, this strategy has worked well for me. I find it allows me to be more concise about what is under test, while preserving info (for other devs & future self) about where the expected return value is coming from.
You can just set an instance variable within the subject block:
context 'as a user' do
subject { #user = FactoryGirl.create(:user) }
its(:name) { should == #user.name }
end

Resources