17

What would be the best approach at unit testing a model that integrates into an application that is tightly coupled to database?

The specific scenario here is a shopping cart - I'd like to be able to test the adding removing and retrieving of items from the cart as well as pricing logic etc. This in my mind all requires database access though I have read several times that database access should be avoided.

2

8 Answers 8

13

Dependency injection is one way of handling this. You can set-up a test database to mimic the shopping cart, or you can even write some code that "confirms" the customer's transaction. Then at runtime, your software will pick which component to connect to.

Just don't connect to the production database for anything during testing!

1
  • 1
    With DI and proper application design, you should be able to test without any database --- provided the mock that you inject provides enough detailed mocking of the back-end database.
    – Peter K.
    Mar 4, 2012 at 15:48
5

In unit test, you have to define the boundary of what you are testing. Unit testing is different from integration testing. If pricing logic is independent from Cart content, then you test that separately. If this is not the case, and all modules are tightly coupled, build a test environment that mimics the production as much as you could and work with that. I don't believe that short cuts and simulation helps on the long run.

0
2

The model should not depend on a (concrete) DB. If it only knows an abstract DB (read "interface") which is handed to the model then you can replace the DB with a mock object.

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts...

1

I had a similar problem - I had no possibility to guarantee my test DB keeps the values. So in future I get e.g other prices.

I extracted the data I needed into a small sqlite-DB and used this DB for my tests. The Test-DB is now part of the setup of my unit-test.

1
  • 2
    The point of unit tests is to test your code in isolation. If you use a sqllite db then its not in isolation. Also inconsistencies between databases can cause errors Mar 4, 2012 at 14:01
0

"Best" is subjective, but you could just use a test db connection.

Use fixtures to load some test data (example products to buy) and then write the test case for the class/function you want to test.

2
  • Describing unit tests which test a function which acts on a database as integration tests is quite misleading @murph.
    – AD7six
    Mar 4, 2012 at 10:01
  • 1
    Ok, now I'm deeply confused - if it involves a database its not by most definitions a unit test because its not self contained. If you have a database then you are running tests at a higher level, one that had dependencies one that looks at "combining" things. Regardless this is not a clear explanation to my mind of how to solve the problem.
    – Murph
    Mar 4, 2012 at 10:27
0

I built a plugin for Symfony 1.4 (PHP) to tackle this problem (among others). It is modeled after the way Django's test framework (Python) operates: the framework builds and populates a separate test database before each test starts, and it destroys the test database after each test completes.

I had a couple of concerns about this strategy, both in terms of performance (if the schema doesn't change, why not simply clear the data instead of rebuilding the entire structure?) and convenience (sometimes I want to inspect the database after a test failure, so don't destroy it indiscriminately!), so I took a slightly different approach.

Before the first test runs, the database is destroyed and rebuilt, in case there have been model changes since the last test. Before each subsequent test runs, the data in the database are cleared out, but the structure is not rebuilt (though a manual rebuild can be triggered from a test if necessary).

By selectively loading data fixtures in each test, one can create the proper environment for that test without interfering with subsequent tests. Fixture files can also be re-used, which makes this task much less onerous (though it is still my least favorite part of writing tests!).

In both test frameworks, the database adapter is configured to use the test connection instead of the "production" connection to prevent test execution from corrupting existing data.

0

I'd say, just go ahead and use fixtures to pre-load the data. It's how unit testing frameworks seem to work in general, when testing the manipulation of data.

But if you really want to avoid having to connect to a database of any sort and go by the overly-strict definition that unit tests don't touch anything outside of the code, take a look at object mocking - it may give you ideas.

For example, instead of dropping the SQL directly in the code where you need it, have a way to call a method that does only what that SQL does. UsePerson.getPhoneNumber(), for example, instead of SELECT phone_number FROM person WHERE id = <foo>. Not only is it cleaner and easier to understand at a glance, but during testing you can mock the Person object so that getPhoneNumber() will always return 555-555-5555 or something, instead of touching the database.

0

This is fairly easy to do with junit if a little long winded.

The "setup" should define and populate a set of temporary tables.

You can then perform the unit tests for all the update, insert, delete functionality.

For each test you call your update method then run some SQL to verify the expected result.

In the "teardown" phase you drop all the tables.

In this way you always run the same tests on the same initial data. If you keep the tables between tests they end up being "polluted" by failed tests, also, a consistent "insert" test is almost impossible as you need to keep inventing new keys on every test.

Not the answer you're looking for? Browse other questions tagged or ask your own question.