[rspec-users] Isolating rails model specs from their implementation

David Chelimsky
2007-07-29T21:49:36Z

On 7/29/07, David Chelimsky <dchelimsky at gmail.com>
wrote:
> On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote:
> > On 29/07/07, David Chelimsky <dchelimsky at gmail.com> wrote:
> > > On 7/29/07, Russell Tracey <russell.tracey at gmail.com>
wrote:
> > > > I''m currently taking a Rails project management app
I built when
> > > > learning Rails and adding specs to it. During the course of
building
> > > > the app the requirement that project should be archiveable
was added.
> > > > So a project is in one of two states active or archived.
> > > >
> > > > This led to the creation of the following methods:
> > > >
> > > > Project.active_projects
> > > > Project.archived_projects
> > > >
> > > > @project.active?
> > > > @project.archived?
> > > > @project.archive!
> > > > @project.unarchive!
> > > >
> > > > The current implementation of this is using a separate table
of
> > > > "visibilities" as follows:
> > > >
> > > > # Implementation 1 (Current)
> > > >
> > > > Tables:
> > > >
> > > >   Project
> > > >     id name           visibility_status_id
> > > >     1  ActiveProject                        1
> > > >     1  ArchivedProject                     2
> > > >
> > > >   VisibilityStatuses
> > > >     id name
> > > >     1  Live
> > > >     2  Archived
> > > >
> > > > But the same behavior could be implemented using a datetime
column as follows:
> > > >
> > > > # Implementation 2
> > > >
> > > > Tables:
> > > >
> > > >   Project
> > > >     id name            archived_at
> > > >     1  ActiveProject   null
> > > >     1  ArchivedProject 2007-07-29:18:57
> > > >
> > > > Or in fact numerous other ways e.g.
> > > >
> > > > # Implementation 3
> > > >
> > > > Army of cows:
> > > >
> > > > Each cow represents a project, the cows wear one of two hats
> > > > to indicate the active/archived status of the project they
represent.
> > > >
> > > > ...and so on.
> > > >
> > > > It''s my understanding that model specs (and specs
in general) should
> > > > be shielded from the implementation details, so how do i
check that
> > > > Project.active_projects only returns active projects without
looking
> > > > at assuming something about the implementation? My initial
thought is
> > > > to check each of them using one of the other exposed methods
above, in
> > > > this case...
> > > >
> > > >   Project.active_projects.all? {|p| p.active? }
> > > >
> > > > but then i can''t work out how to spec all the other
methods without
> > > > going round in circles so that each spec would end up
assuming that
> > > > the other methods work (in this case that p.active? is
working) or
> > > > worse resorting to peeking at implementation details.
> > >
> > > Keep in mind that back-filling examples to existing code is a
very
> > > different process from writing the examples first, which is the
> > > situation for which RSpec is intended. In that case, you might
start
> > > with one example like this:
> > >
> > > describe Project do
> > >   it " should not be active by default" do
> > >     project = Project.create
> > >     project.should_not be_active
> > >   end
> > > end
> > >
> > > Then the next example might be that when you activate it should
be active:
> > >
> > > describe Project do
> > >   it " should not be active by default" { ... }
> > >
> > >   it "should be active after you activate it" do
> > >     project = Project.create
> > >     project.activate!
> > >     project.should be_active
> > >   end
> > >
> > >   it "should show up in the list of active projects when
activated" do
> > >     project = Project.create
> > >     project.activate!
> > >     Project.active_projects.should include(project)
> > >   end
> > > end
> > >
> > > etc.
> > >
> > > In this second pair of examples, we never "test" the
activate! method
> > > in terms of looking at its internal effects (i.e. that it changes
> > > something in the database), but rather through the difference in
the
> > > way the object behaves after having called the activate! method.
> > >
> > > Make sense?
> > >
> > > David
> > > _______________________________________________
> > > rspec-users mailing list
> > > rspec-users at rubyforge.org
> > > http://rubyforge.org/mailman/listinfo/rspec-users
> > >
> >
> > Yes this is making more sense now, the only remaining thing i am
> > wondering is, given the above examples, would specing that
> > active?/archived? be possible without resorting to implementation
> > details that Kyle wrote about. If i''m reading them correctly
the
> > examples above spec out the behavior of activate!, active_projects and
> > the default state of a project after creation, but don''t
define the
> > behavior of active?
> >
> > In this app the projects are actually "active" by default
before being
> > archived at some point later. So they are only ever in one of two
> > states, active or archived.
> >
> > I''m thinking something like this
> >
> > describe Project do
> >   ...
> >   it " Not sure what this would be called?" do
> >    project = Project.create
> >    project.should be_active
> >
> >    project.archive!
> >
> >    project.should_not be_active
> >  end
> > end
>
> Changing state between expectations in one example is a TDD no-no. The
> reason is that if "project.should be_active" fails, you might do
> something to fix it and then find that "project.should_not
be_active"
> fails. If you have them in separate examples then the fact that only
> one fails and not the other gives you better feedback:
>
> describe Project do
>   it "should be active when first created" do
>     project = Project.create
>     project.should be_active
>   end
>
>   it "should not be active after it is archived" do
>     project = Project.create
>     project.archive!
>     project.should_not be_active
>   end
> end

FYI - the two together also tell a more complete story:

Project
- should be active when first created
- should not be active after it is archived

Cheers,
David