Refactoring and using each


#1

Hi all,

I’m new to this list and new to RSpec so I have been trying out RSpec
the
last couple of days and I find it very a natural way of testing. So
first of
all thanks for providing this framework.

Now, I have written some tests for my controllers and models and I saw
myself writing similar code, so I began refactoring and came up with the
following issue.

Here is a simple example of what I first wrote:

describe Example do

it “should not be valid without attribute1” do
Example.new(:attribute2 => “2”).should_not be_valid
end

it “should not be valid without attribute2” do
Example.new(:attribute1 => “1”).should_not be_valid
end
end

Which I rewrote into another working test:

module ExampleSpecHelper
def required_valid_attributes
{:attribute1 => “1”, :attribute2 => “2”}
end
end

describe Example do
include ExampleSpecHelper
[:attribute1, :attribute2].each do |attribute|
before(:all) do
@model_with_one_missing_attribute =
TextMessage.new(required_valid_attributes.except(attribute))
end

it “should not be valid without #{attribute}” do
@model_with_one_missing_attribute.should_not be_valid
end
end

In this example in you might not see difference in lines of code, but
imagine you would have 10 attributes and 10 more tests for each
attribute…
Now I rewrote this I was still not satisfied because I thought I would
like
to use this same approach for several models with different attributes
while keeping the logic of this test in one place. Unfortunately, I
didn’t
come that far because of this:

#this works:
describe Example do
include ExampleSpecHelper
required_attributes = [:attribute1, :attribute2]
required_attributes.each do |attribute|
before(:all) do
@model_with_one_missing_attribute =
TextMessage.new(required_valid_attributes.except(attribute))
end

it “should not be valid without #{attribute}” do
@model_with_one_missing_attribute.should_not be_valid
end
end

#However this which eventually will be more DRY, but does for some
reason
not work?!
module ExampleSpecHelper

def required_attributes
[:attribute1, :attribute2]
end

end

describe Example do
include ExampleSpecHelper
required_attributes.each do |attribute|
before(:all) do
@model_with_one_missing_attribute =
TextMessage.new(required_valid_attributes.except(attribute))
end

it “should not be valid without #{attribute}” do
@model_with_one_missing_attribute.should_not be_valid
end
end

############

I don’t understand why it does not work. In the last example
required_attributes is nil while the other methods from the helper
module
such as ‘required_valid_attributes’ are available on an even lower
level.
Why? I hope you understand why I’m trying to refactor it like this. If I
can
do this I only need to define the required attributes for each model and
use
it_should_behave_like “an AR model” to keep it DRY.

Hope someone can clarify this and that I haven’t done something stupid!
Thanks!

Cheers,

Jeroen


#2

On Wed, Oct 8, 2008 at 6:01 AM, Jeroen van Dijk
removed_email_address@domain.invalid wrote:

Here is a simple example of what I first wrote:
end
describe Example do
end
describe Example do
end
end
@model_with_one_missing_attribute.should_not be_valid
do this I only need to define the required attributes for each model and use
it_should_behave_like “an AR model” to keep it DRY.

Hope someone can clarify this and that I haven’t done something stupid!

There are a couple of problems with this approach. First of all,
before(:all) runs only once per group, whereas before(:each) runs
repeatedly before(:each) example. This means that whatever gets
created before(:all) is shared across examples, whereas what is
created in before(:each) is not.

Generally speaking, you should avoid before(:all) except for setting
up expensive things like database connections.

Also, you can write as many different before(:each) or before(:all)
blocks and they will all be run, so this:

[:attribute1, :attribute2].each do |attribute|
before(:all) do
@model_with_one_missing_attribute =
TextMessage.new(required_valid_attributes.except(attribute))
end
end

is the equivalent of this:

before(:all) do
@model_with_one_missing_attribute =
TextMessage.new(required_valid_attributes.except(:attribute1))
end

before(:all) do
@model_with_one_missing_attribute =
TextMessage.new(required_valid_attributes.except(:attribute2))
end

They get run in order, one time each, resulting in
@model_with_one_missing_attribute missing :attribute2 for every time
the example gets run. The same problem would happen with before(:each)
in this case.

What might work here would be something like this:

describe “this model” do
def valid_attributes
{:first => ‘valid’, :second => ‘valid’}
end

valid_attributes.keys.each do |attribute|
it "should require #{attribute}
@model.new(valid_attributes.dup.delete(attribute)).should_not
be_valid
end
end
end

I’ve seen a number of variations like this posted to this list, so you
might want to search the archives for ‘model validation’.

Also, there are a few plugins that offer ways to do this already:

http://github.com/joshknowles/rspec-on-rails-matchers
http://github.com/pelargir/rspec_validation_expectations

I’m sure there are others.

HTH,
Cheers,
David


#3

On 2008-10-08, at 07:01, Jeroen van Dijk wrote:

Here is a simple example of what I first wrote:
end
describe Example do
end
describe Example do
end
end
@model_with_one_missing_attribute.should_not be_valid
it like this. If I can do this I only need to define the required
attributes for each model and use it_should_behave_like “an AR
model” to keep it DRY.

Hope someone can clarify this and that I haven’t done something
stupid! Thanks!

Cheers,

Jeroen

Hi Jeroen. As I was getting into RSpec and writing specs for my
various models, I too found that my code could be a lot DRYer. I ended
up writing a module that generates specs for my model attributes. I’ve
put it up on GitHub, along with full, lengthy example of how to use
it. Have a look:
http://github.com/nickhoffman/modelspeccer/

Cheers,
Nick