Rspec Class Variable Testing

RSpec Class Variable Testing

class Foo
DEFAULT_ITEMS = %w(foo bar baz)

class << self
attr_accessor :items
end

@items = DEFAULT_ITEMS
end

describe Foo do
before(:each) { Foo.class_variable_set :@items, Foo::DEFAULT_ITEMS }

it "should have a default" do
Foo.items.should eq(Foo::DEFAULT_ITEMS)
end

it "should allow items to be added" do
Foo.items << "kittens"
Foo.items.include?("kittens").should eq(true)
end
end

Or maybe a better way is to reload the class

describe 'items' do
before(:each) do
Object.send(:remove_const, 'Foo')
load 'foo.rb'
end
end

How can I clear class variables between rspec tests in ruby

describe DataFactory
before(:each) { DataFactory.class_variable_set :@@url, nil }
...
end

Rspec testing method that modifies instance variable

Here's a concise example

describe MyClass do
describe "#update" do
it "appends the argument to the array" do
subject.update(value = 'some_value')
expect(subject.ary).to eq([value])
end
end
end

A few notes compared to your initial code. First of all, you can rely on subject. subject is defined as

described_class.new

It's like having

describe MyClass do
subject { described_class.new }

describe "#update" do
...
end
end

or

describe MyClass do
describe "#update" do
subject = described_class.new
end
end

which essentially means

describe MyClass do
describe "#update" do
subject = MyClass.new
end
end

If you just want to test an instance, use subject. Do not use before actions to set instance variables.

The following line

subject.update(value = 'some_value')
expect(subject.ary).to eq([value])

is essentially a shortcut for

value = 'some_value'
subject.update(value)
expect(subject.ary).to eq([value])

It helps me to not write the expected string twice. The rest should be quite easy to understand.

You may also want to test the return value, if this is relevant.

describe MyClass do
describe "#update" do
it "appends the argument to the array" do
subject.update(value = 'some_value')
expect(subject.ary).to eq([value])
end

it "returns the item that was added" do
value = 'some_value'
expect(subject.update(value)).to eq(value)
end
end
end

How to access instance variables in rspec tests

This is how I ended up fixing this. I think this is more along the lines of what some of my commenters were saying. This also provides a little more info on the context of pic

Test:

require 'rails_helper'

RSpec.describe TrelloToAc::Card do

let(:trello_card) do
cassette = 'trello_to_ac/cards/get'
VCR.use_cassette(cassette, match_requests_on: [:method]) do
Trello::Card.find 'itZmoIG4'
end
end

let(:email_sent_label) do
cassette = 'trello_to_ac/labels/get'
VCR.use_cassette(cassette, match_requests_on: [:method]) do
Trello::Label.find '60db99d5230c123a1ab808d0'
end
end

let(:trello_to_ac_card) do
TrelloToAc::Card.new(trello_card, 32188, 147, email_sent_label)
end

describe '#pic' do
it 'returns pic url from trello card' do
cassette = 'trello_to_ac/attachments/get'
result = VCR.use_cassette(cassette, match_requests_on: [:method]) do
trello_to_ac_card.pic
end
expect(result).to be_a String
end
end
end

Actual code:

class TrelloToAc::Card
attr_reader(
:card, :order_number, :list_ac_campaign_id, :email_sent_label
)

def initialize(card, order_number, list_ac_campaign_id, email_sent_label)
@card = card
@order_number = order_number
@list_ac_campaign_id = list_ac_campaign_id
@email_sent_label = email_sent_label
end

def send_email_if_needed
#
#
attach_trello_pic_to_ac_contact if pic
#
#
end

def pic
@pic ||= card.attachments.last.url if card.attachments.any?
end
end

This way I am testing an instance of the class, not instance variables. I have similar tests for other methods as well.

RSpec testing of a class which uses a gem object as an instance variable

In RSpec, 'receive' is a method that accepts a symbol that represents the name of a method. (It allows you to chain a 'with' method that accepts the expected list of parameters.) To fix the before-block you could do this:

before do
allow(subject.instance_variable_get(:@instance_variable1).gemObjectMethod1).to receive(:gemObjectMethod2).with("sample").and_return([])
end

The sheer ugliness of that, though, suggests that something is wrong. And it is. The code is violating the law of demeter pretty badly and the test is being drawn into it.

As a first attempt to clean it up, you might consider a method that "caches" the results of calling @instance_variable1.gemObjectMethod1. Let's say that that first method returns an enumerable group of widgets. You could change your class to include something like this:

def check_validity(a_string)
widgets.gemObjectMethod2(a_string).empty?
end

private

def widgets
@widgets ||= @instance_variable1.gemObjectMethod1
end

Your class still knows a bit too much about the gem object, but now you have broken it down in such a way that you could refactor how you find widgets -- perhaps a different gem or your own implementation of it. For the purposes of your testing, you can isolate that decision from the test by mocking widgets.

let(:gem_widgets) do
instance_double(GemObjectMethod1ResultClass, gemObjectMethod2: true)
end

before do
allow(subject).to receive(:widgets).and_return(gem_widgets)
allow(gem_widgets).to receive(:gemObjectMethod2).with("sample").
and_return([])
end

it 'should pass with "sample"' do
expect(actual).to eql true
end

Class Variables in RSpec

You should stub the role attributes to return the need values:

it "returns child organization as user" do
allow(RootOrganization).to receive(:role).and_return("Admin")
allow(Organization).to receive(:role).and_return("User")

expect(ChildOrganizationOne.role).to eq "User"
end

How do I test an instance variable in a controller?

Your mentors are being stupid. While it can be good to understand controller specs as they are everywhere in legacy applications adding them to new applications is not a good practice.

For new Rails apps: we don't recommend adding the
rails-controller-testing gem to your application. The official
recommendation of the Rails team and the RSpec core team is to write
request specs instead. Request specs allow you to focus on a single
controller action, but unlike controller tests involve the router, the
middleware stack, and both rack requests and responses. This adds
realism to the test that you are writing, and helps avoid many of the
issues that are common in controller specs.

DHH explains pretty well what is wrong with testing the instance variables of your controller and what you should be doing instead:

Testing what instance variables are set by your controller is a bad
idea. That's grossly overstepping the boundaries of what the test
should know about. You can test what cookies are set, what HTTP code
is returned, how the view looks, or what mutations happened to the DB,
but testing the innards of the controller is just not a good idea.

Which is why assigns was extracted to a separate gem. It all boils down to testing what your code does - not how it does it.

See:

  • https://github.com/rspec/rspec-rails/issues/1838
  • https://github.com/rails/rails/issues/22496
  • https://github.com/rails/rails/issues/18950


Related Topics



Leave a reply



Submit