What is this notation in rspec: it { is_expected.to ... } - ruby

I am reading the 'Better specs' page, and in one of the examples it says:
context 'when logged in' do
it { is_expected.to respond_with 200 }
end
context 'when logged out' do
it { is_expected.to respond_with 401 }
end
And I don't recognize this. I usually would do:
context 'when logged out' do
it 'responds with a 401' do
expect(response).to eq(401)
end
end
What is that syntax?

This is something introduced heavily in Rspec 3.XX. It's under the one line syntax guides as outlined here
RSpec supports a one-liner syntax for setting an expectation on the
subject. RSpec will give the examples a doc string that is auto-
generated from the matcher used in the example. This is designed
specifically to help avoid duplication in situations where the doc
string and the matcher used in the example mirror each other exactly.
When used excessively, it can produce documentation output that does
not read well or contribute to understanding the object you are
describing.
This comes in two flavors:
is_expected is defined simply as expect(subject) and is designed for
when you are using rspec-expectations with its newer expect-based
syntax.

it { is_expected.to respond_with 200 }
that is more readable.
Why you added description if you can read it from test.
Your code should be simple, smart and readable in the same time...
but if you realy want, you can add even novel... up to you :)

Related

How to Write mock of method using with to send params

Hi I want to know how can I write rspec for the following
def find_user(content)
user = User.find(content.to_i) ||
User.find(email: content) rescue
nil
end
I tried writing
It "user with user name" do
expect(User).to receive(:find).with(email: "test#a.com").and_return(user)
End
But I am gettig error saying
Argument Error
Block not Passed
Can someone please tell what am i missing
I may look first at your code here.
def find_user(content)
user = User.find(content.to_i) ||
User.find(email: content) rescue
nil
end
What is content? I looks like you're expecting either a user_id or an email address.
Doing this from the console:
irb(main):080:0> User.find("email#email.com".to_i)
=> ActiveRecord::RecordNotFound (Couldn't find User with 'id'=0)
So it seems as if having a generic find_user method may be contributing to some of the test writing confusion.
Many times, overly complex tests point to overly complex code.
Perhaps you need one method
find_user_by_email(email)
and another
find_user_by_id(id)
Also, refer to https://api.rubyonrails.org/v6.1.3.2/classes/ActiveRecord/FinderMethods.html#method-i-find_by
It will automatically return nil if nothing is found.
Start there, And then like the other commenters, then post your class, and the spec and we can go from there.

Testing custom exception with a single expect?

I have a function that I want to test raises an exception on an input, but that exception also carries some more information than just a plain message, and I want to test that too. So I did something like this as seen in the rspec documentation:
it 'raises the correct exception' do
expect { my_call }.to raise_error do |error|
expect(error.some_field).to eq('some data')
end
end
This works great, however it runs afoul of the RSpec/MultipleExpectations cop:
RSpec/MultipleExpectations: Example has too many expectations [2/1]
From what I can tell it is impossible to use raise_error in block form like this without more than one expect, so what gives? Is there some way to somehow save the raised exception outside the example so I can spec it normally, without doing something horrible involving rescue in the specs? Or should I use a custom raise_custom_error matcher?
Rubocop by default I think enables the warning that you see which says to only have one expect in each it block. You can disable this in rubocop.yml by adding this:
# Disables "Too many expectations."
RSpec/MultipleExpectations:
Enabled: false
Or if you only want to disable it for your specific spec you can do so by adding comments like this, note you can disable any rubocop rule this way by using the rule name in comments:
# rubocop:disable RSpec/MultipleExpectations
it 'raises the correct exception' do
expect { my_call }.to raise_error do |error|
expect(error.some_field).to eq('some data')
end
end
# rubocop:enable RSpec/MultipleExpectations
it 'does something else' do
expect(true).to be true
end
For more rubocop syntax options see this answer

RSpec Matchers Not Working (Expect Not Supported)

I have researched this and every thing I've read says that the following should work:
require 'spec_helper'
require 'rspec/expectations'
include RSpec::Matchers
RSpec.describe 'Posts' do
it 'should return 200 response when getting posts' do
result_posts = RestClient.get('http://jsonplaceholder.typicode.com/posts')
expect(result_posts.code).to eq(200)
end
end
I have that in file (json_spec.rb) in my spec directory. This is using RSpec 3.5.4.
The message being received when running this spec is:
only the `receive`, `have_received` and `receive_messages` matchers
are supported with `expect(...).to`, but you have provided:
#<RSpec::Matchers::BuiltIn::Eq:0x007f9b43590f48>
One post suggested that I should be using
extend RSpec::Matchers
rather than trying to "include" them. I did that and the exact same error appears.
Yet another post suggested I should no longer be requiring "rspec/expectations" but rather just "rspec". That doesn't work either. (Yet another post said the exact opposite, of course. But at least I covered my bases there.)
Another post suggested that the include (or maybe the extend or maybe even both) had to go in an RSpec configure block, as such:
RSpec.configure do |config|
include RSpec::Matchers
end
That, however, also does not work.
What you see above is literally all that I have in my spec directory. My spec_helper.rb file initially just contained the require statements and the include directive. I moved them to the actual spec file (as shown above) just to see if that was the issue.
I'm not using Rails or Cucumber so, to my knowledge, there is no wider context in which I can, or should, be including the matchers.
I have to assume I'm missing something fairly fundamental here but none of the RSpec documentation has been much of a roadmap about this particular issue.
Thanks to #MarkoAvlijaĆĄ (see comment to post), the issue was apparently having the explicit require as well as the include statement at all.
Once those were removed, the spec file executed without issue.
I had to remove this line from spec_helper.rb: config.expect_with(:rspec) { |c| c.syntax = :should }

RSpec 3 acceptance testing approach without "its" blocks

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 :)

How do I parameterise RSpec tests so I can test the same behaviour under slightly different conditions

I'm implementing a service that has several different ways it can be accessed:
Using simple query parameters
With parameters encoded as a Javascript object
For some calls both GET and POST are supported, with POST being used when there is large amounts of data being sent to the service.
What's the best way to structure my RSpec tests to avoid unnecessarily repeating code, allowing me to run the same basic assertions each time?
I'm already using shared_examples to capture some comment tests for things like response code, mimetype, etc. But I'm wondering whether there are other options, particularly when I want to invoke the service using all request methods AND a range of expected inputs and outputs.
The way I would do it in this case is to specify the request as a lambda that performs it. That way I can refer to it in my shared specs and set a different one for each type of request.
I like using rspec describe blocks when its sets an expectation, in this case that a particular request method is used. The whole thing will look something like this:
describe FooController do
shared_examples_for "any request" do
it "assigns foo" do
#request.call
assigns[:foo].should == "bar"
end
it "does not change the number of bars" do
#request.should_not change(Bar, :count)
end
end
context "using GET" do
before do
#request = lambda { get "index" }
end
it_should_behave_like "any request"
end
end
An even cleaner way is to use the 'let' construct, although it may be a step too deep in rSpec magic for a novice:
describe FooController do
shared_examples_for "any request" do
it "assigns foo" do
request.call
assigns[:foo].should == "bar"
end
it "does not change the number of bars" do
request.should_not change(Bar, :count)
end
end
context "using GET" do
let(:request) { lambda { get "index" } }
it_should_behave_like "any request"
end
end

Resources