RSpec attribute hash - ruby

Can someone explain to me what is wrong here. Learning RSpec - I am receiving a failed test with message - expected: "Miller" got: nil. I thought that the second 'before block' would simply merge the middle_name with the original #valid_attributes. What is the correct way to do this. I know that I can simply say p.middle_name to assign it but I am trying to learn the concepts of RSpec.
describe Person do
describe "Validations" do
subject { p }
before { #valid_attributes={first_name: "Joe", last_name: "Sample"} }
...
context "with optional middle name" do
let(:p) { Person.new(#valid_attributes) }
before { #valid_attributes.merge({middle_name: "Miller"}) }
its(:middle_name) { should eq("Miller") }
end
end
end

Your problem: Hash#merge returns a new hash, you want Hash#update.
How to do it better? This is not just an advice for testing, but for programming in general: if you update and reuse variables you're gonna hit some problems with state; take a more functional approach. In this particular case you should use factory_girl or similar gem to easily create objects without having a #valid_attributes being updated who knows where.

Related

Multiple let declaration of the same helper method inside of an example group

Is there a way I can declare the same helper method, using let, inside of the same example group, but let it return different values in different expectation?
It would be something like
describe MyClass do
subject(:my_obj) { described_class.new(my_var) }
describe '#my_method' do
context 'some context' do
let(:my_var) { 0 }
# expectation that involves my_var
let(:my_var) { 1 }
# expectation that involves my_var
end
end
end
A solution would be to provide different contexts for each of the expectation, but is it possible to solve this without adding more contexts?
Maybe you can define an array an iterate over it, simply:
describe MyClass do
subject(:my_obj) { described_class.new(my_var) }
describe '#my_method' do
context 'some context' do
[0,1].each do |n|
let(:my_var) { n }
# expectation that involves my_var
end
end
end
end
You could use the access to an example object and its metadata in the let block in combination with the tests metadata:
let(:my_var) do |example|
example.metadata[:my_var]
end
context 'some context' do
it "my_var is 0", my_var: 0 do # <= here we set the necessary metadata
...
end
it "my_var is 1", my_var: 1 do # <= ... and again
...
end
end
...
Unlike explicit sub-contexts, this way of writing specs is relatively rarely used, so it adds some mental overhead for anyone reading this code for the 1st time. But the thing is that deeply nested contexts aren't "free" either (at some nesting level it's just quite hard to understand what's going on), so flattening the spec with some "magic" might make sense - depending on the use case. Personally I'd use sub-contexts if I can and some white magic only if I must...

Expect assertion that is only true for one element in an array [duplicate]

This question already has answers here:
RSpec matcher that checks collection to include item that satisfies lambda
(2 answers)
Closed 3 years ago.
I want to assert that an array contains at least one element that passes an RSpec expectation. But most of the elements in the array will not pass the expectation. So I'd like to do something like this:
it "finds one element that matches" do
array.any? do |element|
expect(element).to eq("expected value")
end
end
And have the test pass if any of the elements pass the expectation. But, of course, the test will fail as I've written it here.
Is there a pattern in RSpec to do what I want to accomplish?
I don't want to do this:
it "finds one element that matches" do
expect(array.any? {|val| val == "expected value"}).to be_true
end
Because it's not clear to me how to manually check the same thing as the matcher I need to use in my test. I want to use the have_attributes matcher, which does some subtle metaprogramming magic I don't want to risk messing up trying to re-implement on my own.
You can use the include matcher to compose matchers:
expect(array).to include(a_string_matching(/foo/))
Despite the somewhat awkward syntax, you can use this with have_attributes:
expect(obj).to have_attributes(tags: include(a_string_matching(/foo/))
But if that's not flexible enough for whatever reason, you can use the satisfy matcher:
expect(array).to satisfy {|arr| arr.any? {|val| val == "expected value"})
The double-nested block is somewhat awkward by itself, but the flexibility of satisfy lets you do all kinds of stuff with it, and you can use the include matcher to get you there. For example:
require "rspec"
require "ostruct"
obj = OpenStruct.new(name: "foobar", tags: %w(bin bazzle))
describe obj do
it "has a bin tag" do
is_expected.to have_attributes(tags: include(/bin/))
end
it "has a tag 3 characters long" do
is_expected.to have_attributes(tags: include(satisfy { |t| t.length == 3 }))
end
end
If you're willing to add a gem, I really like rspec-its for cases like these: they can clean up the specs for individual attributes of an object which don't warrant their own subject block nicely:
describe obj do
its(:tags) { is_expected.to be_a Array }
its(:tags) { is_expected.to include "bin" }
end
RSpec has composing matchers that can be used with expect(array).to include to achieve what I wanted. For example:
expect(array).to include(a_string_matching("expected value"))
For the have_attributes matcher specifically, RSpec has an alias for it called an_object_having_attributes, allowing me to write:
expect(array).to include(an_object_matching(object_i_want_duplicated))

RSpec combine block matcher with non-block matcher

I would like to check if my class is creating a new object and returning an instance of it. My idea was to combine change matcher with be_instance_of matcher but RSpec doesn't allow to do it.
expect { subject.call }.to change { Model.count }.by(1).and be_an_instance_of(Model)
I don't want to split it into two different expects with one without the block to avoid multiple invocations of the same method.
What is the general approach to this kind of situations? How should I handle my case?
You can define subject as subject.call and do this:
specify do
expect { subject }.to change { Model.count }.by(1)
expect(subject).to be_an_instance_of(Model)
end
So if previously your subject was
subject { Foo.new }
make it
subject { Foo.new.call }
let and subject calls are memoized, so it'll be only called once.
There is only one problem: if the first expectation fails - the second will not run (and this is SomethingToAvoidInSpecs™) so consider aggregating failures to remedy it.

How to test Ruby class with default parameters using RSpec

I have a class called Grid:
class Grid
attr_reader :rows, :columns
def initialize(rows=20, columns=20)
#rows = rows
#columns = columns
end
end
I want to test that the rows and columns fields return 20 by default and return whatever integer is supplied otherwise. I don't know the best way to do this.
I "solved" this by creating two Grid instances. One has supplied values for rows and columns. The other one does not have supplied values and thus uses the default values of 20.
require_relative 'spec_helper.rb'
describe Grid do
let(:grid) {Grid.new(15, 15)}
let(:gridNoParameters) {Grid.new()}
subject { grid }
describe "#rows" do
its(:rows) { should eq(15) }
context "when parameter not supplied" do
subject { gridNoParameters }
its(:rows) { should eq(20) }
end
end
describe "#columns" do
its(:columns) { should eq(15) }
context "when parameter not supplied" do
subject { gridNoParameters }
its(:columns) { should eq(20) }
end
end
Is this the best way to test? Any help is appreciated as I am pretty new to Rspec and test driven development.
I would lay out the spec like this:
describe Grid do
context "#initialize" do
context "with parameters" do
let(:grid) { Grid.new(15, 15) }
it "should use specified values" do
expect(grid.rows).to eq 15
expect(grid.columns).to eq 15
end
end
context "without parameters" do
let(:grid) { Grid.new }
it "should use defaults" do
expect(grid.rows).to eq 15
expect(grid.columns).to eq 15
end
end
end
end
Take a look at BetterSpecs for ideas how to organize specs, and to use expect notation, etc. I'm not a huge fan of using subject here, but it's up to you, as it is personal preference.
I think your approach is fine.
You're testing the default
You're testing the non-default
This particular test strikes me as a bit defensive/paranoid, but if it's critical that a test fails if/when someone accidentally changes or removes the default values, then I guess this test is fine.
Testing constructors is always a little weird to me, if all the constructor does is copy the input parameters to the instance variables. It borders on testing that the = operator in Ruby actually works, which is a bit silly. It also borders on what I call, "testing against typos," where the test will only fail if a developer makes a blindingly obvious mistake or removes a default from a method without thinking it through. The thing is, I don't believe that any amount of testing can successfully protect against sloppiness.
There are more complex situations where I might be this careful, but the class you're presenting as an example here is so simple and so straightforward that I don't think it needs anything more than this, personally.

RSpec magic for testing an array's none? method?

I have an Array/Enumerable where each entity is a Hash. Using RSpec what is the most idiomatic way to test for "none of the entries in the enumerable should have a key called 'body'"?
I can do something like:
array.none? {|thing| thing.key? 'body'}.should be_true
or
array.should be_none {|thing| thing.key? 'body'}
...but there must be a more RSpec-way of doing this, correct?
I can't seem to find an appropriate built-in matcher. Is the answer a custom matcher?
I would use
responses.should be_none { |response| response.key? 'body' }
Between the two you gave. This would be slightly more helpful with an error like
"Expected none? to return true"
where as your first example would say something like
"expected False:false class to be true"
The third option I can see would be something like
keys = responses.map { |response| response.keys }.flatten.uniq
keys.should_not include "body"
This would give an error like
expected ["foo", "bar", "body"] not to include "body"
Other than that, looking at https://www.relishapp.com/rspec/rspec-expectations/v/2-11/docs/built-in-matchers/satisfy-matcher
you could try
responses.should satisfy { |response| not response.keys.include? "body" }
Another option would be turning the be_none statement around with be_any:
responses.should_not be_any { |response| response.key? 'body' }
I would assume that the result is equivalent as any? is the negation of none?.
It's mostly a question of which option you feel is the most intuitive to read and, as EnabrenTane mentions, if you think the error messages are helpful enough.

Resources