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
Related
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.
I'm working on some unit tests. One of them use a specific configuration variable as set in my application MyBigApp::Env which looks like:
{:country=>'uk', :another_hosts=>["192.168.99.105"]}
So I access it with MyBigApp::Env.country
However in my unit test I want that country for the test become something.
Using rspec I've seen stub but can't get it to work - any ideas where I'm going wrong:
MyBigApp::Env.stub(:[]).with('country').and_return('gr')
Also tried this (as above shows deprecated):
allow(MyBigApp::Env).to receive('country').and_return('gr')
Infact as a test, I also tried:
my_hash = {:uri=>nil}
allow(my_hash).to receive(:[]).with(:uri).and_return('Over written!')
p my_hash
and that didnt update either - it just returned {:uri=>nil}
As a workaround, at the moment I'm having to save the env var in a temp var in the before(each) block then return it back to the original in the after(each). This feels really risky to me. I'm thinking imagine the service running and someone runs unit tests it could effect the end user in that small instance the test is running.
Any help would be appreciated.
Thanks
Yes it possible, but keep in mind that stub only works when you trigger/call the method that you stubbed/mocked
my_hash = {:uri=>nil}
allow(my_hash).to receive(:[]).with(:uri).and_return('Over written!')
p my_hash[:url] # it will be 'Over written!'
This works for me:
my_hash = {:uri=>nil}
allow(my_hash).to receive(:[]).with(:uri).and_return('Over written!')
expect(my_hash[:uri]).to eq "Over written!"
In your sample test case, you are just calling p my_hash which doesn't actually call the [] method.
In terms of why this isn't working with MyBigApp::Env, well, that really depends on what class it is. Possible whatever method .country is doesn't actually call [].
Really, if you call MyBigApp::Env['country'] and stub MyBigApp::Env to receive [] with 'country', it should work.
In regards to your concern about changing your running application's behavior from the tests ... what kind of tests are these?! Running unit tests against a live production application would be very odd. How do you imagine it would change your production app's code? The Env hash just lives in memory right?
Anyway, you should never have to worry about your tests changing the experience for an 'end user'. Always run tests on a completely quarantined envionment, meaning don't use the same database. Actually, the test database is usually wiped after each test.
Just wanted to suggest a non-stubbing alternative. For example:
def code_under_test
key = 'country'
# ... maybe lots of code
value = MyBigApp::Env[key] # deep inside some classes
# ... lots more code
"This is the #{value}"
end
MyBigApp::Env is hard-coded deep in the code, and the need for stubbing reveals that dependency, and the benefits of OOP encapsulation are lost.
It'd be much easier if this were the case:
def code_under_test(config_vars = MyBigApp::Env)
"This is the #{config_vars['country']}"
end
it 'should return my country value' do
value = previous_code_under_test('country' => 'TEST VALUE')
expect(value).to eq("This is the TEST VALUE")
end
No stubbing required, just plain old method calls.
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
So I've been trying to figure this out, and the best solution I can came up with his global variables - but that seems so dirty and 1974 - Am I missing a feature of Rake/ Test::Unit?
I have a Rake file in which I'm running tests:
Rake::TestTask.new(:test) do |t|
t.test_files = FileList['test_*.rb']
end
and test_1.rb has something like this:
require "test/unit"
class TestStuff < Test::Unit::TestCase
def setup
#thingy = Thing.New(parameter1, parameter2)
end
def test_this_thing
#thing.do()
end
end
My problem is, Thing.new() requires arguments, and those arguments are specific to my environment. In reality, I'm running Selenium-WebDriver, and I want to pass in a browser type, and a url, etc... sometimes I want ff, othertimes I want chrome... sometimes this URL, sometimes that... depending on the day, etc.
The simplest thing seems to do something like:
#all that rake stuff
$parameter1 = x
$parameter2 = y
and then make Thing.new() look up my global vars:
#thingy = Thing.New($parameter1, $parameter2)
This seems sloppy.. and it just doesn't feel right to me. I'm still trying to get this 'test harness' up and running, and want to do it right the first time. That's why I chose Rake, based on a lot of other feedback.
Keep in mind, I'll probably have 100's of tests, ultimately, and they'll all need to do get this information... I thought Rake was good at making sure all of this was easy, but it doesn't seem to be.
Where did I go wrong?
I have used YAML files to store my configuration (browser config, environments including URLs, etc).
You can also use an environmental variable to define simple configurations. You can access environmental variables via ENV['foobar'] in Ruby.
So, for example, my browser call might look like this inside my setup method:
driver = Selenium::WebDriver.for (ENV['SWD_BROWSER'] || "firefox").to_sym
and in my Rake file (or in the shell console) define the environmental variable to use.
In a _spec.rb file I'm setting up an exclusion filter something like:
RSpec.configure do |config|
# we need determine this once at the very front
# and the result be available in the instance
server_success = server1_available?
config.exclusion_filter = {
:svr1 => lambda {|what|
case what
when :available
!server_success
end
}
}
end
and then later in the file I do
describe :get_items_by_client, :svr1 => :available do
to prevent test execution if the server isn't available.
This all works fine if I run the spec file alone. However, I have similar code in another file controlling tests that access a different server, and when I run them all only I see that each of the server checks is done (I have a puts in the "serverX_available?" code), but only one set of tests is being excluded (even though neither server is available).
I'm starting to think that you can have only a single exclusion filter, but I can find any docs anywhere that speak to that. Is this doable on a per-file basis? I could have a single complex filter all in a support file, but then how would I get it incorporated when I'm doing just a run of a single spec-file?
Ideally, I'd like to find a form that works per-file but let's me do the availability check once since it is a somewhat expensive check and I have several examples in the test that are controlled by that.
config.filter_run_excluding :cost => true
config.filter_run_excluding :slow => true
Try this out and this works.