I want to run a given suite of tests several times - once as each of the basic types of system users. (It tests the various features that should be shared by them). More generally, I often end up wanting to run the same set of tests under different conditions.
run_as_each(user_list) do |user|
describe "When visiting the front page with #{user.name}" do
it "should see the welcome message" do
....
end
it "should be able to login" do
....
end
end
end
But this isn't working - It says my method "run_as_each" is undefined - in fact, it seems helpers can only be used inside of the actual specific tests, "it"s. So what should I do?
So what is another way I can approach this problem - specifically, to run a batch of tests for several different users, or at the least to define within a test which users it should be run for?
Have you looked into using shared example? You mentioned types of users, so something like this might work.
shared_examples "a user" do
let(:user) { described_class.new }
describe "When visiting the front page" do
it "should see the welcome message" do
....
end
it "should be able to login" do
....
end
end
end
describe UserTypeA do
it_behaves_like 'a user'
end
describe UserTypeB do
it_behaves_like 'a user'
end
I didn't run the code, so syntax errors are probable.
More from the docs
I was able to get around this problem by including a file with a namespaced module that included all the "helper" methods I needed to use for dynamic code generation. The actual test helpers, if included through the rspec config, are only loaded when the tests are being executed - they are, thus, not available to the any code that occurs outside of tests.
Related
I have two tests that are very similar. In fact, both tests should produce the same results, but for different inputs. Each needs its own before block but in the interest of DRY I'd like them to share the same it block.
Is that even possible? If so, how?
Shared Examples in Rspec are designed to be used for this purpose. You can keep common it blocks inside a shared example and include them in describe or context blocks.
Simplest example of shared_examples would be,
RSpec.shared_examples "unauthorized_response_examples" do
it { expect(subject).to respond_with(403) }
it { expect(json['message']).to eq(I18n.t("unauthorized")) }
end
And inside your controller specs whenever you need to check unauthorized response you can include examples like,
...
include_examples "unauthorized_response_examples"
Also, you can pass on parameters, action names and controller names and have before(:each|:all) hooks and nested contexts or describe.
For more, you can look at rspec documentation.
Helper methods. (Excuse the horribleness of the example. Would have been better if you'd posted yours :P)
describe "soup" do
def soup_is_salty # helper method! \o/
soup.add(:meat)
soup.add(:egg)
soup.cook
soup.salty?
end
describe "with carrot" do
before(:all) do
soup.add(:carrot)
end
it "should be salty" do
soup_is_salty # get help from helper method! \o/
end
end
describe "soup with potato" do
before(:all) do
soup.add(:potato)
end
it "should be salty" do
soup_is_salty # get help from helper method! \o/
end
end
end
Take the block and create and external method
for example I have some tests that require me to login to my app. So I have a helper.rb file that I include in each spec and that contains a "login" block. Then in each test I can just call login
I Wrote a general testing file which includes a few tests.
I want to write a few files that will use those tests, each file will run specific tests (not all of them).
A test example:
class AccountBase
describe "TC_ACCOUNT" do
it "Fill info Fields" do
#test here
end
end
end
Lets call it baseTests.rb.
Now I have another file - infoTest.rb .
How can I call "Fill info Fields" from within infoTest
Thanks.
It sounds like you need Rspec's shared examples:
Shared examples on Relish,
or in the Rspec API docs
Your example might look something like:
class AccountBase
shared_examples "TC_ACCOUNT" do
it "Fill info Fields" do
#test here
end
end
end
And then you could include the shared examples in your infoTest.rb file with:
include_examples "TC_ACCOUNT"
or:
it_behaves_like "TC_ACCOUNT"
The shared example can then be written to check metadata from the context that includes it or accept parameters to its block that are passed in by the including spec, so as to change its behaviour based on which test file it's being included into.
I maintain a gem that gathers data from various APIs. For APIs that require an API key, I have implemented scrapers to fall back on when no API key is set.
My current solution to testing both the scraper and the API code is to have two spec files that are virtually the same except for a before hook.
In the API spec, I have a before(:each) hook that explicitly sets the API key.
In the scraper spec, I have a before(:each) hook that explicitly nullifies the API key.
Aside from those two different hooks, the specs are essentially the same. However, there are one or two instances where the API spec differs from the scraper spec, so I'd need the ability to handle that. I imagine just checking to see whether or not the API key is set will be enough for this.
Another thing to note is that I don't want to rerun my entire test suite, as only a very small fraction of the codebase has both an API and scraper. I basically need to be able to set it on a per-spec basis.
Thanks in advance for any help that you guys can provide!
EDIT: Think I have a solution. Will provide details when I get a full implementation.
This is what RSpec's shared examples are for. Use like so:
RSpec.shared_examples_for "an API component" do
let(:api_client) { APIClient.new }
it "foos" do
expect(api_client).to foo
end
it "bars" do
expect(api_client).to bar
end
end
RSpec.describe API do
it_behaves_like "an API component" do
before(:each) { set_api_key }
end
end
RSpec.describe Scraper do
it_behaves_like "an API component" do
before(:each) { nullify_api_key }
end
end
I've a very strange problem when I was writing some tests with should_receive and should_not_receive for a legacy project.
It was using rspec-rails 2.10.x, which can be upgraded to lastest rspec-rails 2.x. But since the code is complex, we can't reproduce it easily into a small clean project.
The problem is, we have such code in test:
let(:user) { mock(...) }
it "should do something" do
user.should_receive(:name=)
put :update, :user_id=>1, { :name => "newname" }
end
it "should not do something" do
user.should_not_receive(:name=)
put :update, :user_id=>1, { :name => "newname" }
end
These two tests are both passed but their only difference is one using should_receive and another one using should_not_receive.
I have done some researches and found some guy have similar problems:
RSpec for should_receive and should_not_receive both passed for Exception
https://github.com/rspec/rspec-mocks/issues/164
Now I am looking for some workaround to still be able to check if a method has been called or not. Any ideas?
The problem is that in the controller test, you have a different instance of user that in the controller itself.
Add the line puts user.object_id to the spec, and puts #user.object_id to the controller, and you will see this. (Different object_id's means they are not the same object.)
Even though they refer to the same entity (i.e. they both were loaded using the same record in the database and have the same id), they are different instances. A more general way to see this is the following:
user == User.find(user.id) # => true
user.object_id == User.find(user.id).object_id # => false
should_receive is failing because it tests that you call :name= on the instance in the test, which you don't. But you may actually be calling :name= on the instance in the controller.
should_not_receive is passing because it tests that you never call :name= on the instance in the spec. But this isn't what you want to test. You want to test that you never call :name= on the instance in the controller.
This is the risk with negative assertions in general. False positives are common in tests like there.
A better way to test would be to verify side-effects of the methods. For example:
expect{ put :update, :user_id=>1, { :name => "newname" }}.
to change{user.reload.name}.from(...).to(...)
# You'll have to fill in the `...`
Now, it doesn't matter whether it did this by calling user.name= or by setting the name some other way (user[:name] = ..., user.public_send(:name=, "...", etc.). If you changed the implementation in the controller, you want it to continue to pass the test, because what you really care about is whether the name changed in the DB. But the way you've written the tests now, even once you get them passing, they would fail if you changed the controller implementation.
I have a series of RSpec tests - each one living in their own files - that all need a specific object to be ran (shop).
Since setting up the shop object is complicated, I want to have a master file that builds the shop object and then passes it to all the RSpec test files in turn. The idea is that those writing the test scripts do not need to know anything about the setup step.
I could not figure out how to bring something from the outside world into an rspec test case. I have tried variations around the lines of this:
file master.rb:
describe "Testing tests/some_test.rb" do
before :all do
#shop = some_complex_operations
end
load 'tests/some_test.rb'
end
file tests/some_test.rb:
describe 'Some feature' do
it 'should know about #shop'
expect(#shop).not_to be nil
end
end
When I run rspec master.rb then some_test.rb fails.
I know what I outlined is probably a very wrong way to go about this, but I hope at least you get the idea.
How can I implement such a thing cleanly?
What you need is a global hook.
You can create a spec_helper and add there a before all global hook. For example:
RSpec.configure do |config|
config.before(:each) do
#shop = "in case you need a fresh instance of shop each time"
end
config.before(:all) do
#shop = "in case you need a single instance (i.e. you don't modify it)"
end
end
Then you should require this helper from each of your specs. The spec_helper can store any global hooks as well as helpers, or other dependencies for your tests.
When you are writing a test, it should test one thing. shop might be a very complex object, which many objects and methods interact with, but I should guess none of them are aware of the complexity of shop - each method is interested in some aspect of shop.
Therefore I suggest that instead of building a complex object for each test, make a double of it, make it expect what's relevant, and then simply stub the rest:
describe 'Some feature' do
let(:shop) { double('shop').as_nil_object }
let(:my_object) { MyClass.new(shop: shop) }
it 'should do something awesome with shop' do
expect(shop).to receive(:awesome_data).and_return(my_data: 'is_this')
expect(my_object.do_something_awesome).to eq 'how awesome is_this?'
end
end