I'm having some trouble with Rspec accepting the scope of one of my let definitions.
Here's my test, the classes all work fine as when I have them print out they're sweet.
code removed due to silly error
Thanks in advance!
You're missing the do and end for your it block:
describe "Initialisation of order within User object" do
it "Creates an order property set to an empty array" do
expect(user.instance_variable_defined?(:#orders)).to be(true)
expect(user.instance_variable_get(:#orders)).to eq([])
end
end
Related
I really like the self-documenting nature of RSpec tests so I often create many nested contexts and examples in order to clarify the intent of the tested objects and the situation in which they are being tested, like in this simplified example:
RSpec.describe Foo do
... some let definitions ...
context 'when used properly' do
before do
something_expensive_to_calculate
end
it 'is successful' do
...
end
it 'has benefits' do
....
end
it 'has the power to change the world' do
...
end
end
context 'in evil hands' do
... more nested contexts and examples ...
end
end
Such approach, however, seems to repeat the setup phase for each of the examples and because of that slows the tests down considerably.
The question is whether there are some patterns which allow to add a documentation text to a group of assertions (like I use 'it'), but which would not set up the context as 'it' does.
I found before(:all) blocks. Unfortunately let variables cannot be used in them and so it is not a solution for me. Another solution would be let variables with context lifetimes, but it seems there is no such thing in RSpec.
TestProf has something that you seem to need https://test-prof.evilmartians.io/#/recipes/let_it_be
let_it_be(:foo) { very_expensive_setup }
Which would instantiate the :foo once and keep its state for all the examples.
Make sure you also ready through caveates as this is not just simply better let
I'm unsure if I properly understand your question but perhaps what you are looking for is the subject syntax ?
RSpec.describe Foo do
... some let definitions ...
context 'when used properly' do
subject { do_some stuff }
it 'whatever' do
expect(something).to_not eq true
expect(subject).to eq false
end
end
end
The subject clause allows you to take a series of actions relating to something you are trying to test and re-use the actions inside the subject clause over and over again.
So I'm pretty new to Rspec and I'm trying to figure out how to write tests for a class that takes an object as a constructor parameter and sets that object to an instance variable. Then it calls that instance variable's object methods in other methods.
Example:
class ClassA
def initialize(string_object, gem_object)
#instance_variable1 = gem_object
#string = string_object
end
def check_validity?(some_arg)
unless #instance_variable1.gemObjectMethod1.gemObjectMethod2(some_arg).empty?
return true
end
false
end
..
..
end
I feel very lost in how to write specifications for this. For one I don't really understand what specifying a constructor actually entails. What I realize is that I'd have to find some way of mocking or stubbing the gem_object I'm getting as argument, but I'm not sure how.
For the next method, what I've tried to this point is:
describe '#check_validity?' do
context 'gets empty list' do
let (:actual) { subject.check_validity?("sample") }
before do
allow(subject).to receive(#instance_variable1.gemObjectMethod1.gemObjectMethod2).with("sample").and_return([])
end
it 'returns false' do
expect(actual).to be false
end
end
end
But this gives me error relating to my constructor saying that it expected 2 arguments but was given 0.
Any help would be much appreciated! Also, I couldn't really find anything on line about specifying constructors with their arguments mocked. Maybe I'm looking in the wrong place or maybe missing something obvious as this is my first experience with BDD.
In RSpec, 'receive' is a method that accepts a symbol that represents the name of a method. (It allows you to chain a 'with' method that accepts the expected list of parameters.) To fix the before-block you could do this:
before do
allow(subject.instance_variable_get(:#instance_variable1).gemObjectMethod1).to receive(:gemObjectMethod2).with("sample").and_return([])
end
The sheer ugliness of that, though, suggests that something is wrong. And it is. The code is violating the law of demeter pretty badly and the test is being drawn into it.
As a first attempt to clean it up, you might consider a method that "caches" the results of calling #instance_variable1.gemObjectMethod1. Let's say that that first method returns an enumerable group of widgets. You could change your class to include something like this:
def check_validity(a_string)
widgets.gemObjectMethod2(a_string).empty?
end
private
def widgets
#widgets ||= #instance_variable1.gemObjectMethod1
end
Your class still knows a bit too much about the gem object, but now you have broken it down in such a way that you could refactor how you find widgets -- perhaps a different gem or your own implementation of it. For the purposes of your testing, you can isolate that decision from the test by mocking widgets.
let(:gem_widgets) do
instance_double(GemObjectMethod1ResultClass, gemObjectMethod2: true)
end
before do
allow(subject).to receive(:widgets).and_return(gem_widgets)
allow(gem_widgets).to receive(:gemObjectMethod2).with("sample").
and_return([])
end
it 'should pass with "sample"' do
expect(actual).to eql true
end
I'm just writing some algorithm methods that I don't want to put in a class. So I just put them in my ruby file.
But I cant figure out how to write test or more specifically use describe :xxx since I dont' have a class name to put after the main describe. Any ideas?
You can put any string after the describe statement:
describe "Something You Want To Test" do
# Your specs here
end
I'm currently refactoring a whole load of cucumber tests to use a "Page Object" pattern, but I'm having a lot of problems using the RSpec matchers.
The existing step I have is as follows:
Then /^I should (not )?see the following alerts:$/ do |negate, alerts|
expectation = negate ? :should_not : :should
within(ALERT_TABLE_ID) do
alerts.hashes.each do |alert|
page.send(expectation, have_content(alert["Original ID"]))
end
end
end
My refactored step is:
Then /^I should (not )?see the following alerts:$/ do |negate, alerts|
expectation = negate ? :should_not : :should
#alert_reporting_panel = AlertReportingPanel.new(Capybara.current_session)
#alert_reporting_panel.verify_contents expectation, alerts
end
And my Panel Object is:
class AlertReportingPanel
def initialize(session)
#session = session
end
def verify_contents(expectation, alerts)
#session.within(ALERT_TABLE_ID) do
alerts.hashes.each do |alert|
#session.send(expectation, have_content(alert["Original ID"]))
end
end
end
end
Unfortunately, I get undefined method 'have_contents' for #<AlertReportingPanel:0x3f0faf8> (NoMethodError).
I have tried adding require 'rspec' to the top of the class and also tried fully qualifying the have-content method thus: Capybara::RSpecMatchers::HaveMatcher.have_content, but I just get uninitialized constant Capybara::RSpecMatchers (NameError).
I'm pretty new to Ruby and I'm sure this is trivial to fix... but I just can't seem to work it out for myself.
Please help. Thankyou.
This was a while back so I'm guessing you may have your answer by now but here goes.
You need to include the necessary modules in order bring in and have access to the likes of *have_content*. So your Panel Object would look like:
class AlertReportingPanel
include Capybara::DSL
include Capybara::Node::Matchers
include RSpec::Matchers
def initialize... etc
Instead of writing your own Page Object system you could try using SitePrism
I'm a little biased (I wrote that gem) but it might make life easier for you.
Much like this question, I too am using Ryan Bates's nifty_scaffold. It has the desirable aspect of using Mocha's any_instance method to force an "invalid" state in model objects buried behind the controller.
Unlike the question I linked to, I'm not using RSpec, but Test::Unit. That means that the two RSpec-centric solutions there won't work for me.
Is there a general (ie: works with Test::Unit) way to remove the any_instance stubbing? I believe that it's causing a bug in my tests, and I'd like to verify that.
As it happens, Mocha 0.10.0 allows unstubbing on any_instance().
str = "Not Stubbed!"
String.any_instance.stubs(:to_s).returns("Stubbed!")
puts str.to_s # "Stubbed!"
String.any_instance.unstub(:to_s)
puts str.to_s # "Not Stubbed!"
Mocha does not provide such a functionality. However you can implement it yourself.
The first thing we should know about mocha is that mocha actually replaces the original methods when you stub them. So in order to be able to restore these methods later, you must keep a reference to the former ones. It can be easily achieved by: alias new_method old_method.
It must be done before mocking the old_method.
Now, to unmock a method, you only need to alias old_method new_method.
Consider the following code:
class A
def a
true
end
end
class TestA < Test::Unit::TestCase
def test_undo_mock
a = A.new
A.class_eval {alias unmocked_a a}
A.any_instance.stubs(:a).returns("b")
assert a.a, "b"
A.class_eval {alias a unmocked_a}
assert a.a, "a"
end
end
If you want to remove all your stubs/expectations in one go, then you can do that using mocha_teardown (eg. call self.mocha_teardown).
May be a little bit destructive in this case, however.