RSpec 3 acceptance testing approach without "its" blocks - ruby

I have a doubt about how to test a simple CSV importer without using the its(:...) clause.
In RSpec 2.x, my approach was to set the imported object as the subject of my spec, and then test each attribute in a its(...) block. It was an acceptance-like test, but it served me well, and I didn't want to unit test the library I used to do my CSV parsing, as it was really a trivial implementation, so I was ok with an end-to-end test.
Now, with RSpec 3, I can make this spec pass with transpec, but I read the explanation about why the its block has been removed and I think RSpec 3 is suggesting a different approach, right? So how would you test that?
I don't think a lot of ugly blocks like this
describe '#email' do
subject { super().email }
it { is_expected.to eq("john_doe#email.com") }
end
are any better than
its(:email) { should == "john.doe#email.com" }
as they do exactly the same thing.
I've read that you need to test "behaviour", but how about acceptance tests? What's the suggested way to go here?
Thanks!

From what I understand, Myron suggests using rspec-given for a one-liner rich test suite. Using this package, your tests will look something like this:
Given(:email) { subject.email }
context "sign up" do
When { subject.sign_up(email: "john.doe#email.com") }
Then { email == "john.doe#email.com" }
end

While the its functionality has been removed from rspec-core, it has been put into an includable gem, rspec-its.
https://github.com/rspec/rspec-its
I would just include this gem and keep writing tests the way you have been - I find them the most readable.
ps. Unrelated but I would also always use eq instead of == in specs :)

Related

How can I mock a Ruby "require" statement in RSpec?

I have a Ruby cli program that can optionally load a user-specified file via require. I would like to unit test this functionality via RSpec. The obvious thing to do is to mock the require and verify that it happened. Something like this:
context 'with the --require option' do
let(:file) { "test_require.rb" }
let(:args) { ["--require", "#{file}"] }
it "loads the specified file"
expect(...something...).to receive(:require).with(file).and_return(true)
command.start(args)
end
end
(That's just typed, not copy/pasted - the actual code would obscure the question.)
No matter what I try, I can't capture the require, even though it's occurring (it raises a LoadError, so I can see that). I've tried a variety of things, including the most obvious:
expect(Kernel).to receive(:require).with(file).and_return(true)
or even:
let(:kernel_class) { class_double('Kernel') }
kernel_class.as_stubbed_const
allow(Kernel).to receive(:require).and_call_original
allow(Kernel).to receive(:require).with(file).and_return(true)
but nothing seems to hook onto the require
Suggestions?
So require is defined by Kernel but Kernel is included in Object so when you call require inside this context it is not necessarily the Kernel module that is processing the statement.
Update
I am not sure if this exactly solves your issue but it does not suffer from the strange behavior exhibited below:
file = 'non-existent-file'
allow(self).to receive(:require).with(file).and_return(true)
expect(self).to receive(:require).with(file)
expect(require file).to eq(true)
Working Example
OLD Answer:
This is incorrect and exists only for posterity due to the up-votes received. Some how works without the allow. Would love it if someone could explain why as I assumed it should raise instead. I believe the issue to be related to and_return where this is not part of the expectation. My guess is we are only testing that self received require, with_file, and that the and_return portion is just a message transmission (thus my updated answer)
You can still stub this like so:
file = 'non-existent-file.rb'
allow_any_instance_of(Kernel).to receive(:require).with(file).and_return(true)
expect(self).to receive(:require).with(file).and_return(true)
require file
Since I am unclear on your exact implementation since you have obfuscated it for the question I cannot solve your exact issue.

Rspec - Is it possible to stub a hash

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.

Is there a way to automatically update an RSpec expectation?

I'm wondering if this feature exists in RSpec. I can't seem to find any results when looking into it.
What I'd like to do is something that can be done in Jest tests like so:
// This is a Jest expectation
expect(foo).toMatchInlineSnapshot()
// On the first execution of this code, the value of foo will fill in the expectation and result in something like this
expect(foo).toMatchInlineSnapshot('bar')
I'd love to be able to do this with RSpec tests.
# Here's an RSpec expectation
expect(foo).to eq({ bar: 25 })
Say that I make a change to my code that will result in foo[:bar] having a different value, but I don't know what that value will be.
Currently, I need to re-run my tests and see an error saying something along the lines of
Failure/Error: expect(foo).to eq({ bar: 25 })
expected: { bar: 25 }
got: { bar: 100 }
After that, I need to manually update my expectation in order for it to pass.
Is there anyway to tell RSpec to automatically update the expected value?
For Example:
expect(foo).to eq({ bar: 25 }, {update: true})
would change the code after running the test to match the correct value and result in the following code replacing the above expectation:
expect(foo).to eq({ bar: 100 })
Is there any existing way to accomplish this? Some command that I can run with RSpec maybe?
spec UPDATE_EXPECTS=1
I've seen libraries that can match based on snapshots, but haven't been able to find anything that results in the expected answer appearing inline.
Thanks for the help
I don't believe this type of tool exists for RSpec. The closest you could maybe come is using a let or an instance variable at the top of your script.
let(:subtotal) { 25.0 } # can _maybe_ only update this if logic changed
let(:cart) { Cart.new }
before do
cart.add_item(10.0)
cart.add_item(15.0)
end
it do
expect(cart.subtotal).to eq(subtotal)
end
You'd still have to manually curate the values when logic changes that would break the test assertion. There are already a number of matchers in RSpec that will let you do a looser assertion, such as be_within, but nothing to automatically fill in values for you. The include matcher is another powerful matcher that lets you do looser assertions.
I feel like updating values automatically would be equivalent to testing expect(x).to eq(x). I'm not sure there's much value in that kind of test for what I've typically seen RSpec used for.
The reality is, your test should/will often fail when you make a change to your code. It's annoying that you have to go back and fix the test, but it should also reassuring that the test provided some value. I would be more alarmed if I changed code and didn't have a failing test...

Can I programmatically "emit" test cases with xUnit?

Is it possible to use something other than reflection and the [Fact] attribute on a test method to expose tests to xUnit test runner? For example, I'd like to do something like:
[FactSource] // just making this up
public IEnumerable<ITest> GetUnitTests()
{
yield return new TestCase("test case 1", () => FooAssertion());
yield return new TestCase("test case 2", () => BarAssertion());
}
I've wanted to do this many times to reduce the boilerplate of a function to wrap every single case. Usually it makes sense, but when I am testing 100 API endpoints it's the difference between a file with 100 lines vs. 400 lines of code. Also, I have cases where I want to load the tests from a .JSON or .XML file so it would be great if there was another way to load the tests rather than just [Fact] or [Theory] attributes.
NOTE: [Theory] works great for some tests like this, but it doesn't work for loading the cases from a file or for the case I demonstrate above where I am using lambda expressions.
Thank you!
Check out Exude. It does exactly what you want.

What would be a convenient way to count the number of HTTP requests made by a block of code?

Suppose I want to be able to write a test like this:
lambda {
do_something_involving_web_requests
}.should make(1).http_requests
It seems to me there would be several possible ways to implement this kind of functionality; however, it also seems that:
Someone might have already done so (in which case I want to look into their solution); or
Someone on StackOverflow might have an idea I haven't thought of.
So, has this been done already? And/or what are your ideas?
If you're interested in more granular testing of how your app responds to specific HTTP responses, rather than simply counting requests, you can use mocks. Here's how to use RSpec's mocks to test http requests:
#mock_http = mock("http")
Net::HTTP.stub!(:start).and_yield #mock_http
#mock_http.should_receive(:get).with("/")
One library I use is Fakeweb. Fakeweb does what #Joe mentions: it hooks into Net::HTTP and can be configured to return a canned response from a given URL. Many other HTTP libs depend on Net::HTTP so this technique has broad compatibility. Fakeweb example from its docs:
FakeWeb.register_uri(:get, "http://example.com/test1", :body => "Hello World!")
Net::HTTP.get(URI.parse("http://example.com/test1"))
=> "Hello World!"
Neither of these methods have a simple access count though, if you want that you can use rspec-mocks which has the following method count functionality (these can be used on stubs or test doubles):
double.should_receive(:msg).once
double.should_receive(:msg).twice
double.should_receive(:msg).exactly(n).times
double.should_receive(:msg).at_least(:once)
double.should_receive(:msg).at_least(:twice)
double.should_receive(:msg).at_least(n).times
double.should_receive(:msg).at_most(:once)
double.should_receive(:msg).at_most(:twice)
double.should_receive(:msg).at_most(n).times
double.should_receive(:msg).any_number_of_times

Resources