How to Define a Simple Global Variable in an Rspec Test That Can Be Accesed by Helper Functions

is it good to use global variables in specs?

Global variables are discouraged, in RSpec, and in general. Here's why:

Modifying a global variable anywhere affects code everywhere.

So, if a test ever modified the $now variable, all subsequent tests would be affected by the modification. This could lead to some very hard to debug test failures. Hard to debug, because the test would be correct. But, the value of the global would not. So, you could not find the bug in the tests by simple inspection. You'd have to debug it at runtime.

Automated tests need to be independent of each other. This is why RSpec has the lazily executed let statement. let allows you to define a variable within the context of a single example. Here's what the RSpec docs say:

Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.

What this means is that if you define now like this:

let(:now) { DateTime.parse('2020-01-01 00:00:01 -0500') }

The value is guaranteed to be exactly what you say it is in every single test. Because the value is memoized on a per example basis, one test can never affect the value that another test receives when executing.

If you're still not convinced, I would recommend using a global constant over a global variable. At least then, Ruby would warn you if you tried to change the value.

Set variable once for all examples in RSpec suite (without using a global variable)

for this purpose I usually write custom modules that I can include in the spec_helper.rb file.

Let's say that I am testing a back-end API and I don't want to parse every time the JSON response body.

spec/
spec/spec_helper.rb
spec/support/request_helper.rb
spec/controllers/api/v1/users_controller_spec.rb

I first define a function in a module placed under the support subfolder.

# request_helper.rb
module Request
module JsonHelpers
def json_response
@json_response ||= JSON.parse(response.body, symbolize_names: true)
end
end
end

Then I include this module by default for some test types

#spec_helper.rb
#...
RSpec.configure do |config|
config.include Request::JsonHelpers, type: :controller
end

Then I use methods defined in the test.

# users_controller_spec.rb
require 'rails_helper'

RSpec.describe Api::V1::UsersController, type: :controller do
# ...

describe "POST #create" do

context "when is successfully created" do
before(:each) do
@user_attributes = FactoryGirl.attributes_for :user
post :create, params: { user: @user_attributes }
end

it "renders the json representation for the user record just created" do
user_response = json_response[:user]
expect(user_response[:email]).to eq(@user_attributes[:email])
end

it { should respond_with 201 }
end
end

In your case, you could create a module such as

module EnvHelper
def is_debug_mode?
@debug_mode ||= ENV['DEBUG']
end
end

Then you can include it and simply use the method is_debug_mode? in your tests.

Rails: RSpec: using global path variables only seems to work within it blocks

Yes, named routes work only within it or specify blocks. But it's easy to modify the code:

def should_be_stable(path)
get path
response.status.should be(200)
end

describe "StaticPages" do
describe "Home => GET" do
it { should_be_stable(root_path) }
end
end

You steel need to include url_helpers

Rspec: How to assign instance variable in controller spec

When testing private methods in controllers, rather than use send, I tend to use an anonymous controller due to not wanting to call the private method directly, but the interface to the private method (or, in the test below, effectively stubbing that interface). So, in your case, perhaps something like:

require 'spec_helper'

describe TestController do
controller do
def test_some_method
some_method
end
end

describe "a phone test with some_method" do

subject { controller.test_some_method }

context "when my_variable is not nil" do
before { controller.instance_variable_set(:@my_variable, Phone.new(...)) }
it { should be_true }
end

context "when my_variable is nil" do
before { controller.instance_variable_set(:@my_variable, nil) }
it { should_not be_true } # or should be_false or whatever
end
end
end

There's some good discussion on the issue of directly testing private methods in this StackOverflow Q&A, which swayed me towards using anonymous controllers, but your opinion may differ.

RSpec - Using `let` in spec helper

You might consider other approaches:

  • Use mock objects instead of real ones.
  • Refactor the initializer and extract the slow operation to another method

Mock objects of course bring their own set of drawbacks; they can become stale and make tests more brittle. But for some tests that is not an issue.

Refactoring initializers is a favorite of mine. E.g.

MyBigObject.new(args)

becomes

MyBigObject.new(args).setup

or :load_data or :connect_to_database_on_the_moon or whatever is taking a long time. You get the picture.

Obviously this means changing your code, but I find that often works out to be helpful in other ways, and it certainly makes testing easier.

How to declare a variable shared between examples in RSpec?

You should use before(:each) or before(:all) block:

describe Thing do
before(:each) do
@data = get_data_from_file # [ '42', '36' ]
end

it 'can read data' do
expect(@data.count).to eq 2
end

it 'can process data' do
expect(@data[0].to_i).to eq 42
end
end

The difference is that before(:each) will be executed for each case separately and before(:all) once before all examples in this describe/context. I would recommend you to prefer before(:each) over before(:all), because each example will be isolated in this case which is a good practice.

There are rare cases when you want to use before(:all), for example if your get_data_from_file has a long execution time, in this case you can, of course, sacrifice tests isolation in favor of speed. But I want to aware you, that when using before(:all), modification of your @data variable in one test(it block) will lead to unexpected consequences for other tests in describe/context scope because they will share it.

before(:all) example:

describe MyClass do
before(:all) do
@a = []
end

it { @a << 1; p @a }
it { @a << 2; p @a }
it { @a << 3; p @a }
end

Will output:

[1]
[1, 2]
[1, 2, 3]

UPDATED

To answer you question

describe MyClass do
before(:all) do
@a = []
end

it { @a = [1]; p @a }
it { p @a }
end

Will output

[1]
[]

Because in first it you are locally assigning instance variable @a, so it isn't same with @a in before(:all) block and isn't visible to other it blocks, you can check it, by outputting object_ids. So only modification will do the trick, assignment will cause new object creation.

So if you are assigning variable multiple times you should probably end up with one it block and multiple expectation in it. It is acceptable, according to best practices.



Related Topics



Leave a reply



Submit