Forum: RSpec Refactoring and using each

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
3e626f6013805e96a95e999fc691b21f?d=identicon&s=25 Jeroen van Dijk (Guest)
on 2008-10-08 13:02
(Received via mailing list)
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
5d38ab152e1e3e219512a9859fcd93af?d=identicon&s=25 David Chelimsky (Guest)
on 2008-10-08 15:03
(Received via mailing list)
On Wed, Oct 8, 2008 at 6:01 AM, Jeroen van Dijk
<jeroentjevandijk@gmail.com> 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
49de4cd2f26705785cbef2b15a9df7aa?d=identicon&s=25 Nick Hoffman (Guest)
on 2008-10-09 21:27
(Received via mailing list)
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
This topic is locked and can not be replied to.