NullDB 0.0.1

== What

NullDB is a Rails database connection adapter that interprets common
database operations as no-ops. It is the Null Object pattern as
applied to database adapters.

== How

Once installed, NullDB can be used much like any other ActiveRecord
database adapter:

  ActiveRecord::Base.establish_connection :adapter => nulldb

NullDB needs to know where you keep your schema file in order to
reflect table metadata. By default it looks in
RAILS_ROOT/db/schema.rb. You can override that by setting the
+schema+ option:

  ActiveRecord::Base.establish_connection :adapter => nulldb,
                                          :schema => foo/myschema.rb

There is a helper method included for configuring RSpec sessions to
use NullDB. Just put the following in your spec/spec_helper.rb:

  Spec::Runner.configure do |config|
    ::NullDB.insinuate_into_spec(config)
  end

You can also experiment with putting NullDB in your database.yml:

  unit_test:
    adapter: nulldb

However, due to the way Rails hard-codes specific database adapters
into its standard Rake tasks, you may find that this generates
unexpected and difficult-to-debug behavior. Workarounds for this are
under development.

== Why

NullDB is intended to assist in writing fast database-independant unit
tests for ActiveRecord classes. For why you would want to test your
models without the database, see:
http://www.dcmanges.com/blog/rails-unit-record-test-without-the-database.

NullDB was inspired by the ARBS[http://arbs.rubyforge.org/] and
UnitRecord[http://unit-test-ar.rubyforge.org/] libraries. It differs
from them in a couple of ways:

1. It works. At the time of writing both ARBS and UnitRecord were
   not working for me out of the box with Rails 2.0.

2. It avoids monkey-patching as much as possible. Rather than
   re-wiring the secret inner workings of ActiveRecord (and thus being
   tightly coupled to those inner workings), NullDB implements the
   same [semi-]well-documented public interface that the other standard
   database adapters, like MySQL and SQLServer, implement.

3. UnitRecord takes the approach of eliminating database interaction
   in tests by turning almost every database interaction into an
   exception. NullDB recognizes that ActiveRecord objects typically
   can't take two steps without consulting the database, so instead it
   turns database interactions into no-ops.

One concrete advantage of this null-object pattern design is that it
is possible with NullDB to test +after_save+ hooks. With NullDB, you
can call +#save+ and all of the usual callbacks will be called - but
nothing will be saved.

== Limitations

* It is *not* an in-memory database. Finds will not work. Neither
  will #reload, currently.
* It has only the most rudimentery schema/migration support. Complex
  migrations will probably break it.
* Lots of other things probably don't work. Patches welcome!

== Who

NullDB was written by Avdi Grimm <mailto:avdi@avdi.org>

== Where

* Homepage: http://avdi.org/projects/nulldb/
* Public SVN: http://svn.avdi.org/nulldb/

== Changes

* Version 0.0.1 (2007-02-18)
   - Initial Release

Avdi,

This sounds like a cool idea. One question. Would the usage pattern
then be to use a mock/stub for the find methods and let the other save
methods fall though to the NullDB connector?

Ed

Ed Howland wrote:

Avdi,

This sounds like a cool idea. One question. Would the usage pattern
then be to use a mock/stub for the find methods and let the other save
methods fall though to the NullDB connector?

Yes, that's accurate. Check the documentation for the latest revision,
too; there are some new RSpec helpers.

Avdi Grimm wrote:

Ed Howland wrote:

Avdi,

This sounds like a cool idea. One question. Would the usage pattern
then be to use a mock/stub for the find methods and let the other save
methods fall though to the NullDB connector?

Yes, that's accurate. Check the documentation for the latest revision,
too; there are some new RSpec helpers.

--
Avdi

Do you have any advice on how to setup NullDB in a JRuby environment?
I'm using JRuby 1.5.1, Test::Unit, Shoulda, and will be using
Factory_Girl for functional and integration tests. I'm fairly new to
unit testing so as much step by step as you can give would be great!

Cheers,
Eric

Avdi Grimm wrote:

== What

NullDB is a Rails database connection adapter that interprets common
database operations as no-ops. It is the Null Object pattern as
applied to database adapters.

[...]

== Why

NullDB is intended to assist in writing fast database-independant unit
tests for ActiveRecord classes. For why you would want to test your
models without the database, see:
http://www.dcmanges.com/blog/rails-unit-record-test-without-the-database.

I'm not at all convinced that this is a good way of testing (and in my
experience, ThoughtWorks' software has lots of problems, which could say
something about their testing practices). However, assuming for the
moment that it is good...
[...]

* It is *not* an in-memory database. Finds will not work. Neither
  will #reload, currently.

So how is this of any use at all? Normally, when I use tests with
database operations, I do so to confirm that I'm writing to the database
what I think I am. (Sure, I could use RSpec's mocks to test that -- and
then I wouldn't need NullDB in the first place.) By removing the
capability to save records, it seems to me that you've removed any
utility that this might have had. Am I missing something? How do *you*
find this puppy useful?

Best,

Marnen Laibow-Koser wrote:

Avdi Grimm wrote:

== What

NullDB is a Rails database connection adapter that interprets common
database operations as no-ops. It is the Null Object pattern as
applied to database adapters.

[...]

== Why

NullDB is intended to assist in writing fast database-independant unit
tests for ActiveRecord classes. For why you would want to test your
models without the database, see:
http://www.dcmanges.com/blog/rails-unit-record-test-without-the-database.

I'm not at all convinced that this is a good way of testing (and in my
experience, ThoughtWorks' software has lots of problems, which could say
something about their testing practices). However, assuming for the
moment that it is good...
[...]

* It is *not* an in-memory database. Finds will not work. Neither
  will #reload, currently.

So how is this of any use at all? Normally, when I use tests with
database operations, I do so to confirm that I'm writing to the database
what I think I am. (Sure, I could use RSpec's mocks to test that -- and
then I wouldn't need NullDB in the first place.) By removing the
capability to save records, it seems to me that you've removed any
utility that this might have had. Am I missing something? How do *you*
find this puppy useful?

Best,
--
Marnen Laibow-Koser
http://www.marnen.org
marnen@marnen.org

Sent from my iPhone

Marnen,

I'm fairly new to unit testing and TDD, but I think I understand the
concept of what they're talking about here. The point is that if you are
writing to the database during unit tests, you're actually testing the
database adapter and abstraction layer, in this case: ActiveRecord, and
not the logic that you have written. ActiveRecord, has been tested
extensively, it would be redundant to test it again. By removing the
database from the testing, you can focus on testing your business
logic... and you have the side benefit of it being quicker, which is
essential for TDD to be a practical way to develop software. I think
what you're describing is considered to be functional testing or
integration test, not unit testing. Functional and integration testing
is equally important, it just occurs at a different level.

This is one of the *rare* times when I think Ruby got it wrong when
compared with other languages, most other more mature languages Java,
C++, etc.. provide tools to avoid hitting the database during unit
testing for just this reason. I think Rails, and Ruby in general is
heading in this direction too. The next generation of ORM databases like
DataMapper have this sort of thing built in. Hope this helps!

Cheers,
Eric

I'm fairly new to unit testing and TDD, but I think I understand the
concept of what they're talking about here. The point is that if you are
writing to the database during unit tests, you're actually testing the
database adapter and abstraction layer, in this case: ActiveRecord, and
not the logic that you have written. ActiveRecord, has been tested
extensively, it would be redundant to test it again. By removing the
database from the testing, you can focus on testing your business
logic... and you have the side benefit of it being quicker, which is
essential for TDD to be a practical way to develop software. I think
what you're describing is considered to be functional testing or
integration test, not unit testing. Functional and integration testing
is equally important, it just occurs at a different level.

This is one of the *rare* times when I think Ruby got it wrong when
compared with other languages, most other more mature languages Java,
C++, etc.. provide tools to avoid hitting the database during unit
testing for just this reason. I think Rails, and Ruby in general is
heading in this direction too. The next generation of ORM databases like
DataMapper have this sort of thing built in. Hope this helps!

Cheers,
Eric

Oh, and I almost forgot to mention that Factory_Girl does this as well.
Using Factory.build() you only write to memory and not the database.
Factory_Girl can also allow writing to the database for functional and
integration testing, so you get the best of both worlds. It's one of the
pieces in my testing suite.

-Eric

Eric Schmitt wrote:

Marnen,

I'm fairly new to unit testing and TDD,

I've been doing test-first development as long as I've been doing Rails
development, nearly 3 years. (Whether that means that I too am new to
unit testing is left as an exercise for the student.)

? but I think I understand the

concept of what they're talking about here. The point is that if you are
writing to the database during unit tests, you're actually testing the
database adapter and abstraction layer, in this case: ActiveRecord, and
not the logic that you have written.

That is commonly believed, but the situation is IMHO not that
open-and-shut. Read on.

ActiveRecord, has been tested
extensively, it would be redundant to test it again. By removing the
database from the testing, you can focus on testing your business
logic...

Not always. Often, removing the DB from testing makes it *harder* to
focus on testing the logic.

Some examples may be helpful here. Of course, it's asinine for me to do

User.create! :name => 'John'
User.find_by_name('John').should_not be_nil

as that would indeed be testing ActiveRecord. But consider

class Sandwich
  def make_blt
    Ingredient.create(:name => 'bacon')
    Ingredient.create(:name => 'lettuce')
    Ingredient.create(:name => 'tomato')
  end
end

I think it is entirely reasonable to test this with

describe Sandwich
  describe 'make_blt'
    it "should create bacon, lettuce, and tomato" do
      Sandwich.make_blt

      ['bacon', 'lettuce', 'tomato'].each do |name|
        Ingredient.find_by_name(name).should_not be_nil
      end
    end
  end
end

because this is not testing ActiveRecord; rather, it's testing that I
made the proper *calls* to ActiveRecord.

To be sure, in this case, I could just as easily do
Ingredient.should_receive(:create).with(:name => 'bacon'), but that's
only possible because the method is so simple, and to my mind it's a
little smelly anyway: since the method being tested should be treated as
a black box, I'd rather verify the final state than chase all the method
calls that got us there. And the only way to verify the final state is
to have something tracking that state -- like, oh, say, a DB.

and you have the side benefit of it being quicker, which is
essential for TDD to be a practical way to develop software.

An in-memory DB has similar benefits while not discarding state.

I think
what you're describing is considered to be functional testing or
integration test, not unit testing.

The test I wrote above would certainly be considered a unit test.

[...]

This is one of the *rare* times when I think Ruby got it wrong when
compared with other languages, most other more mature languages Java,
C++, etc.. provide tools to avoid hitting the database during unit
testing for just this reason.

This is a Rails and ActiveRecord issue, not a Ruby one.

I think Rails, and Ruby in general is
heading in this direction too. The next generation of ORM databases like
DataMapper have this sort of thing built in.

DataMapper is an ORM, not an "ORM database". In what respect does
DataMapper's testing differ here? (I've never used DataMapper, though
it certainly looks interesting.)

Hope this helps!

Only by tending to confirm that some of the argument here is fallacious
(unless Avdi has a better one).

Cheers,
Eric

Best,

Marnen,

You make some interesting points... I think I still have a ton to learn
about unit testing and TDD best practices. I'm still just getting
started. Thanks for the info!

Warmest Regards,
Eric

1. Why are we suddenly discussing a two year old post here? Clue me in.

2. Myron Marsten is now the maintainer of NullDB. See
http://github.com/nulldb/nulldb

3. I'm not really interested in rehashing the arguments for NullDB.
Enough teams are happily using it that I had to transfer maintainership
over to someone else so that I could focus on other projects. I suggest
you go on the RSpec-User's mailing list and ask if someone wants to
explain why they use it.

But briefly, I'll say this: if you are including the DB, you aren't Unit
Testing. Period. Your tests might be *useful* and *important*, but they
aren't Unit Tests. A lot of Rails practices, while overall encouraging
more TDD, have muddied the waters WRT to what is a Unit Test. A Unit
test isolates a *single* object or method from *all* of its
collaborators, and tests the inputs of that object or method in a
vacuum. If it doesn't isolate, it's not a Unit Test.

Which, again, is not to say it's a bad test. These days I drive my app
development almost exclusively from high-level full-stack Cucumber
acceptance tests, and only drop down to the Unit level when there is
some particularly interesting/tricky logic to be described. And
sometimes I write functional level tests that include the DB but not the
UI level. But I don't call them unit tests and I keep them separate from
my unit tests. If you are interested in this kind of Unit Test
discipline - which, when followed, tends to make for smaller,
tightly-cohesive objects - NullDB may be for you. Especially because it
enables you to test (for instance) logic in after_save hooks which would
otherwise force the DB to get involved.

Avdi Grimm wrote:

1. Why are we suddenly discussing a two year old post here? Clue me in.

My apologies! It appeared at the top of my list feed and I neglected to
check the date. I'm not sure why it appeared at the top of my feed.

[...]

But briefly, I'll say this: if you are including the DB, you aren't Unit
Testing. Period. Your tests might be *useful* and *important*, but they
aren't Unit Tests. A lot of Rails practices, while overall encouraging
more TDD, have muddied the waters WRT to what is a Unit Test.

Yeah, I'm aware of that.

A Unit
test isolates a *single* object or method from *all* of its
collaborators, and tests the inputs of that object or method in a
vacuum. If it doesn't isolate, it's not a Unit Test.

And there's an argument to be made that since the AR framework is part
of an AR class, it must be respected -- and hit *some* state-preserving
DB-like thing -- to properly unit test an AR class.

Which, again, is not to say it's a bad test. These days I drive my app
development almost exclusively from high-level full-stack Cucumber
acceptance tests, and only drop down to the Unit level when there is
some particularly interesting/tricky logic to be described.

I do similarly.

And
sometimes I write functional level tests that include the DB but not the
UI level. But I don't call them unit tests and I keep them separate from
my unit tests. If you are interested in this kind of Unit Test
discipline - which, when followed, tends to make for smaller,
tightly-cohesive objects - NullDB may be for you.

Got some sample code in this style? I'd like to look at it, even though
I think (a priori) that it is a recklessly dangerous way to test AR
classes. I just might learn something. :slight_smile:

Especially because it
enables you to test (for instance) logic in after_save hooks which would
otherwise force the DB to get involved.

I *want* the DB to get involved if I'm testing the DB lifecycle of my
objects.

--
Avdi

Best,

Marnen Laibow-Koser wrote:

And there's an argument to be made that since the AR framework is part
of an AR class, it must be respected -- and hit *some* state-preserving
DB-like thing -- to properly unit test an AR class.

This is the kind of muddling of concepts that I think Rails encourages.
The idea that "Model == Database Thingy". At every opportunity I
encourage developers to start their apps with bare, pure Ruby models and
only add "< ActiveRecord::Base" in a later iteration. This practice
helps to start thinking of your objects purely in terms of how they
model your domain - which is how OO is supposed to work. After you spend
some time looking at the models strictly in terms of their capabilities
and responsibilities, then you add in persistence. Usually this means
that you add a collaborator, which is some kind of OO/RDB mapping
system. But it's still just another collaborator, with neat, clean
division of responsibilities.

Of course, AR makes it harder to think of it as a collaborator because
you have to inherit from it and it adds all sorts of methods to self and
imposes arbitrary rules like "no initializers". But if you are careful
you can still think of it as a (rather pushy) collaborator and structure
your tests accordingly - separating the tests of persistence from the
tests of logic.

I'll add a post about the how and the why and the benefits of this kind
of strict separation to my queue on Virtuous Code.

Avdi Grimm wrote:

Marnen Laibow-Koser wrote:

And there's an argument to be made that since the AR framework is part
of an AR class, it must be respected -- and hit *some* state-preserving
DB-like thing -- to properly unit test an AR class.

This is the kind of muddling of concepts that I think Rails encourages.
The idea that "Model == Database Thingy".

That's not an idea that I have. I used to do MVC without an ORM (and
indeed, without "official" objects), and I'm quite used to writing
non-AR models in Rails.

At every opportunity I
encourage developers to start their apps with bare, pure Ruby models and
only add "< ActiveRecord::Base" in a later iteration.

That seems like more trouble than it's worth with Rails, but I'll think
about how I'd try that.

This practice
helps to start thinking of your objects purely in terms of how they
model your domain - which is how OO is supposed to work.

No argument there.

[...]

Of course, AR makes it harder to think of it as a collaborator because
you have to inherit from it and it adds all sorts of methods to self and
imposes arbitrary rules like "no initializers". But if you are careful
you can still think of it as a (rather pushy) collaborator and structure
your tests accordingly - separating the tests of persistence from the
tests of logic.

Again, I do that anyway. But if I'm testing features that touch the
persistence layer, then I want an actual persistence layer to talk to,
not just a black hole. And the way Rails structures its objects, I
think that's still a unit test.

If Rails worked a bit differently and used a separate persistence
library instead of an ORM, then I'd agree with you on the appropriate
way to test AR objects. But it doesn't, so I think I don't.

I'll add a post about the how and the why and the benefits of this kind
of strict separation to my queue on Virtuous Code.

I will be interested to read that. At the moment, this separation just
seems like fighting Rails to no advantage and some potential harm.

--
Avdi

Best,