Setting a variable once for each time rspec is run - ruby

I am writing a Ruby gem that accesses the features of a web based API. I need to set up an object which will be initialized and log into the API just once for each time the tests are run. before(:all) is still excessive because it will run once for every describe block, and what I want is something that universally sets up once for all of the test files.
UPDATE
Just as a follow up, to make the object I was using available in the tests, I had to add a setting to the rspec config like this
config.add_setting :client
config.before(:suite) do
RSpec.configuration.client = TDAmeritradeApi::Client.new
RSpec.configuration.client.login
end
And then in the describe blocks I do this:
let(:client) { RSpec.configuration.client }

I believe you are looking for before(:suite) and you can use it in the config section of your spec_helper.rb.
RSpec.configure do |config|
config.before(:suite) do
# API login
end
end

You can use before(:suite) to run a block of code before any example groups are run. This should be declared in RSpec.configure
Source: http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/Hooks

Related

Set RAILS_ENV in rspec

Please considering following code:
class MyModel
validate my_validation unless ENV["RAILS_ENV"] == "test"
end
We have a validation that is going to have major effect on HUGE parts of the test-suite. I only want it to be executed in prod, not when running the test suite*... EXCEPT for the actual tests regarding this validation.
So when testing the validation I need to set the ENV["RAILS_ENV"] to something else then test. I tried this in my my_model_spec.rb-file:
it "tests the validation" do
ENV["RAILS_ENV"] = "development"
# Tests the validation..
ENV["RAILS_ENV"] = "test"
end
This sets the variable while in the spec file, BUT where the check is made in my_model.rb the ENV["RAILS_ENV"] still returns "test".
Is there a way to achieve the declaration of ENV["RAILS_ENV"] in the SPEC-file and have that still set when the model code is executed during the example run?
Yes yes, please believe me we have this under control (... I think :D). It is during a maintenance window.
Obligatory:
validate my_validation unless ENV["RAILS_ENV"] == "test"
In 99.9% of cases, this is really not a good idea.
Just felt I needed to make that clear, in case future readers see this post and get funny ideas... (It would be much better to update the test suite to remain valid, e.g. by changing the factories.)
Is there a way to achieve the declaration of ENV["RAILS_ENV"] in the SPEC-file
Yes - you can stub the value:
allow(ENV).to receive(:[]).with('RAILS_ENV').and_return('development')
There are also some other approaches you could consider.
For example, why not call the method directly, for the purpose of running this test?
record = MyModel.new # or using FactoryBot.build / whatever
record.my_validation
Or, you could add a model attribute to forcibly-run the validation:
class MyModel
attr_accessor :run_my_validation
validate my_validation if ENV["RAILS_ENV"] != "test" || run_my_validation
end
# and in the test:
record = MyModel.new # or using FactoryBot.build / whatever
record.run_my_validation = true
expect(record.valid?).to be_true
Yet another approach you could consider, to eliminate rails environment check from the production code, would be to set an environment-specific configuration value. Which, again, you could stub in the spec:
class MyModel
validate my_validation if Rails.configuration.run_my_model_validation
end
# and in the test:
allow(Rails.configuration).to receive(:run_my_model_validation).and_return(true)
Another benefit to the above is that you could enable the validation in development mode, without making any code change to the application.

Ruby-Rspec: Initialize PageObjects once in spec_helper instead of each spec

I variables set for xpaths in a file called PageObjects. Each spec I run I initialize the page objects with "p = PageObjects.new". However, I would like to initialize "p = PageObjects.new" once in "spec_helper.rb" instead of each spec.
This still gives me "error: uninitialized constant PageObject"...
require 'selenium-webdriver'
require 'yaml'
require 'rspec/retry'
require 'pry'
require 'bundler/setup'
p = PageObject.new
RSpec.configure do |config|
config.default_sleep_interval = 1
config.default_retry_count = 4
config.verbose_retry = false
config.display_try_failure_messages = true
config.exceptions_to_retry = [Net::ReadTimeout, Capybara::ElementNotFound]
end
Is there a way to achieve my goal by initializing PageObject once inside spec_helper rather than in each spec?
RSpec helpers seems to be the perfect solution for you
define the helper.rb
module Helpers
def p
#page_object ||= PageObject.new
end
end
Configure RSpec to include it:
RSpec.configure do |c|
c.include Helpers
end
And then you can use p method that will give you the PageObject:
specify do
expect(p).to be_a(PagObject)
expect(p.object_id).to eq(p.object_id)
end
You effectively want your test database to be maintained between tests. This is dangerous for a number of reasons, the most obvious being previous tests will affect future ones. As you're dealing with the same PageObject you will need to reset it between tests.
Putting that to one side, the options for enabling / disabling this can be found at:
https://relishapp.com/rspec/rspec-rails/docs/transactions, namely:
When you run rails generate rspec:install, the spec/rails_helper.rb
file includes the following configuration:
RSpec.configure do |config|
config.use_transactional_fixtures = true
end
The name of this setting is a bit misleading. What it really means
in Rails is "run every test method within a transaction." In the
context of rspec-rails, it means "run every example within a
transaction."
The idea is to start each example with a clean database, create
whatever data is necessary for that example, and then remove that data
by simply rolling back the transaction at the end of the example.
Disabling transactions If you prefer to manage the data yourself, or
using another tool like database_cleaner to do it for you, simply tell
RSpec to tell Rails not to manage transactions:
RSpec.configure do |config|
config.use_transactional_fixtures = false
end

What is the RSpec equivalent of Behat profiles

I need to alter env variables eg URLS, depending on the environment (alpha, beta, and public/ops) in which the tests are to be run. Are "contexts" within RSpec that allow for this?
I am not sure if I understand the problem you try to solve. Normally RSpec runs in only in the test environment, not in multople environments.
Your test environment is defined in config/environments/test.rb. In than file you define everything that is valid for all kind of tests. For example you will never ever want to send emails from your test suite. Therefore you will find something like this in that file:
config.action_mailer.delivery_method = :test
If there is something that you want to change in your specs (if DelayedJobs should be available or not in the following example), then there are multiple ways to do so. The one I see most is to stub objects to return the values you need for a certain test:
describe 'a complex operation' do
context 'with DelayedJobs' do
# acts like DelayedJobs are configured to run in background
before { Delayed::Worker.stub(:delay_jobs => true) }
it 'creates a DelayedJob' do
...
end
end
context 'without DelayedJobs' do
# acts like DelayedJobs are configured to run immediately
before { Delayed::Worker.stub(:delay_jobs => false) }
it 'calls the complex query' do
...
end
end
end

What's the best practice to store variables for Cucumber tests?

What's the best practice to store variables for Cucumber tests?
I'm using Cucumber and Capybara to test a Java application.
In the application there are many types of users and other data that I need to pass into my Capybara step definitions. So my question is what is the best practice to store these variables/users IDs?
I do have some variables (#instance_variabless) that I've stored all in the env.rb file.
But is there a more clean/elegant way to do this? Like have a dedicated file just for variables that I can call, if so how would such a file be setup?
Please provide examples of solutions?
You can use hooks for set-up and tear-down in hooks.rb file:
#user_ids_and_passwords = YAML.load(File.open('features/config/users.yml'))
#my_heavy_object = HeavyObject.new
#my_heavy_object.do_it
at_exit do
#my_heavy_object.undo_it
end
AfterConfiguration do |config|
puts "Features dwell in #{config.feature_dirs}"
#instance_variable = 'my variable'
end
Before('#some_features') do
#feature_instance_variable = 'something else'
end

Good strategy for testing a lot of reads on the same data with RSpec?

I am testing a class, with RSpec, that reads data from the database. Depending on arguments, it will not return the same.
My current strategy is something like this:
before do
# create a bunch of data
end
it 'test1' # ...
it 'test2' # ...
it 'test3' # ...
Which of course means that my data will be created before each test.
I would like to be able to create the data once, in the scope of this file, and do my reads on the data.
I tried setting use_transactional_fixtures to false for the file and use database_cleaner but it made my tests twice as slow because I had to re-seed my db before and after the tests.
I was wondering if there was a way to tell rspec "run each of these tests in the same transaction".
Or maybe, since I'm having a hard time finding that, there's a better strategy for that kind of testing?
It looks like using the database_cleaner gem was the right thing to do, I was just doing it wrong. I had set my cleaning strategy to truncation which emptied the db...
On this Github issue, David Chelimsky explains that using database_cleaner is the way to go https://github.com/dchelimsky/rspec-rails/issues/2
My tests now look like this:
before(:all) do
self.use_transactional_fixtures = false
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.start
create_data
end
after(:all) do
DatabaseCleaner.clean
end
def create_data
# create the data...
end
it 'test1' # ...
it 'test2' # ...
it 'test3' # ...
end
The same tests now run in ~3.5s versus ~7s before. I am happy :)
edit: one before :all is enough

Resources