How can two specs share the same "it" block? - ruby

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

Related

How to pass an external object to rspec tests?

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

Rspec - How to write specs for a chain of methods

I'm learning rspec, and I'm wondering what the most effective way to write specs for a method that calls a chain of other methods. For example:
class Example1
def foo(dependency)
dependency.bar("A")
dependency.baz("B")
dependency.bzz("C")
end
end
Ideally I would like to write specs like this:
it "should call bar" do
ex = Example1.new
dep = mock
dep.should_receive(:bar).with("A")
ex.foo(dep)
end
it "should call baz"
...
it "should call bzz"
...
When I do that, however, I (understandably) get exceptions like 'unexpected method call baz'.
So what's the best way to deal with that? I have come up with a couple of ideas but I don't know if any of them are good.
Make the mock dependency an "as_null_object" so it ignores the extra calls. (Down side - if I was calling unwanted random stuff on that object, I wouldn't know it)
Stub out the two unused dependency method calls in each spec (Down side - feels very DRY)
Stub out all three dependency calls in a 'before' (Down side - puts a lot of junk in the 'before')
It sounds like you have already worked out which options RSpec gives you. I would go with option 1 and use as_null_object. It's true that you might be missing other random method calls on that object but I would be ok with that if the point of each of these tests was simply to assert that a particular method was being called, especially if I have higher level integration tests covering this method.
If you really need to verify that no other methods are called on dependency then option 3 may make sense but such tests can be brittle when implementation changes.
As an aside, to make your test a little simpler you can use subject to avoid explicitly instantiating Example1 (assuming you are using a describe Example1 block), e.g.:
subject.foo(dep)
(However see discussion in comments - an implicit subject can hide intention).
RSpec has a feature called stub_chain: https://www.relishapp.com/rspec/rspec-mocks/v/2-0/docs/stubs/stub-a-chain-of-methods
What about testing them all in one example?
it "should call bar"
ex = Example1.new
dep = mock
dep.should_receive("bar").with("A")
dep.should_receive("baz").with("B")
dep.should_receive("bzz").with("C")
ex.foo(dep)
end
I believe you can use RSpec to verify the order in which they are called, if that matters.
However, this kind of approach often indicate that there is a problem with how the code is written, e.g. a Law Of Demeter violation. In your example, foo should be a methed on the dependency's class.
I would test this code in this way:
describe "checking foo method" do
before(:each) do
#ex = Example1.new
#test = ClassOfDependency.any_instance
#test.as_null_object
end
after(:each) do
#ex.foo(dependency)
end
it "should call bar method" do
#test.should_receive(:bar).with("A")
end
it "should call baz method" do
#test.should_receive(:baz).with("B")
end
it "should call bzz method" do
#test.should_receive(:bzz).with("C")
end
end
But I'm not sure that it will work, hope it'll give you some ideas.

Rspec: when the matcher fails

I have a Rails project and use RSpec as a testing framework. What I need is to subscribe to the event when some matcher fails, e.g. I got this:
true.should be_false
I want to do some action on every fail. Is this functionality provided by the RSpec?
You can monkey-patch this behavior into RSpec::Core::Example class:
require 'spec_helper'
class RSpec::Core::Example
def failed?
!#exception.nil?
end
end
describe "my_tested_things" do
...
end
Then you can ensure it will run your desired code after all failing matches:
after(:each) do
run_my_code if example.failed?
end
Check out the hooks, not certain it will help but where it passes example in to the block (in the docs) you may be able to get the result...
https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/hooks

How do I stub out a specific file with rspec?

I've searched far and wide and I hope someone can answer this question. I'm using the following code to stub out the 'exists?' method for FileTest in an rspec spec:
it "returns no error if file does exist" do
#loader = MovieLoader.new
lambda {
FileTest.stub!(:exists?).and_return(true)
#loader.load_file
}.should_not raise_error("File Does Not Exist")
end
What I really want to do is to ensure that the existence of a very specific file is stubbed out. I was hoping something like this would do the job:
it "returns no error if file does exist" do
#loader = MovieLoader.new
lambda {
FileTest.stub!(:exists?).with(MovieLoader.data_file).and_return(true)
#loader.load_file
}.should_not raise_error("File Does Not Exist")
end
However, this doesn't seem to be working. I am having a very difficult time finding documentation on what the 'with' method actually does. Perhaps I'm barking up the wrong tree entirely.
Can someone please provide some guidance?
The RSpec stubbing framework leaves a bit to be desired, and this is one of those things. The stub!(:something).with("a thing") ensures that each time the something method is called that it receives "a thing" as the input. If it receives something other than "a thing", RSpec will stop the test and report an error.
I think you can achieve what you want, you'll just have to approach this a little differently. Instead of stubbing out FileTest, you should be stubbing out a method on your #loader instance, and that method would normally call FileTest.exists?. Hopefully this demonstrates what I'm getting at:
class MovieLoader
def load_file
perform_loading if file_exists?(file_path)
end
def file_exists?(path)
FileTest.exists? path
end
end
Your test would then look like:
it "returns no error if file does exist" do
#loader = MovieLoader.new
lambda {
#loader.stub!(:file_exists?).with(MovieLoader.data_file).and_return(true)
#loader.load_file
}.should_not raise_error("File Does Not Exist")
end
Now you are only stubbing one instance of the loader, so other instances will not inherit the stubbed version of file_exists?. If you need to be more fine-grained than that, you'll probably need to use a different stubbing framework, which RSpec supports (stubba, mocha, etc).

With Rspec, run test for each of several options?

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.

Resources