10

In my new project, I decided to try with TDD. And in very beginning I encountered a problem. First thing that I want to do in my application is to give ability to read data from data source. For this purpose, I want to use repository pattern. And now:

  • If test are for real implementation of repository interface, I will be testing class that has access to database, and I know that I should avoid that.
  • If test are for not real implementation of repository pattern, I Will be testing well... just mock. There will be no any piece of production code tested in those unit tests.

I'm thinking about this from two days and still cannot come up with any reasonable solution. What I should do?

11

What a repository does is translate from your domain onto your DAL framework, such as NHibernate or Doctrine, or your SQL-executing classes. This means that your repository will call methods on said framework to perform its duties: your repository constructs the queries needed to fetch the data. If you're not using an ORM-framework (I hope your are...), the repository would be the place where raw SQL-statements are built.

The most basic of these methods is the save: in most cases this will simply pass the object from the repository onto the unit of work (or the session).

public void Save(Car car)
{
    session.Save(car);
}

But let's look at another example, for example fetching a car by its ID. It might look like

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

Still not too complex, but you can imagine with multiple conditions (get me all the cars made after 2010 for all brands in the 'Volkswagen' group) this gets tricky. So in true TDD fashion you need to test this. There are several ways to do this.

Option 1: Mock the calls made to the ORM framework

Sure, you can mock the Session-object and simply assert that the right calls are made. While this tests the repository, it is not really test-driven because you are just testing that the repository internally looks the way you want it to. The test basically says 'the code should look like this'. Still, it is a valid approach but it feels like this kind of test has very little value.

Option 2: (Re)build the database from the tests

Some DAL-frameworks give you the ability to build the complete structure of the database based on the mapping files you create to map the domain onto the tables. For these frameworks the way to test repositories is often to create the database with an in-memory database in the first step of the test and add objects using the DAL-framework to the in-memory database. After this, you can use the repository on the in-memory database to test if the methods work. These tests are slower, but very valid and drive your tests. It does require some cooperation from your DAL-framework.

Option 3: Test on an actual database

Another approach is to test on an actual database and isolate the unittest. You can do this in several ways: surround your tests with a transaction, clean up manually (would not recommend as very hard to maintain), completely rebuild the database after each step... Depending on the application you are building this may or may not be feasible. In my applications I can completely build a local development database from source control and my unittests on repositories use transactions to fully isolate the tests from each other (open transaction, insert data, test repository, rollback transaction). Every build first sets up the local development database and then performs transaction-isolated unittests for the repositories on that local development database. It's a little slower then a pure Unittest but the tests are extremely valuable and catch a lot of issues.

Don't test the DAL

If you are using a DAL framework such as NHibernate, avoid the need to test that framework. You could test your mapping files by saving, retrieving and then comparing a domain object to make sure everything is okay (be sure to disable any sort of caching) but it's not as required as a lot of other tests you should be writing. I tend to do this mostly for collections on parents with conditions on the children.

When testing the return of your repositories you could simply check to see if some identifying property on your domain object matches. This can be an id but in tests it's often more beneficial to check a human readable property. In the 'get me all the cars made after 2010....' this could simply check that five cars are returned and the license plates are 'insert list here'. Added benefit is that it forces you to think about sorting AND your test automatically forces the sorting. You'd be surprised how many applications either sort multiple times (return sorted from the database, sort before creating a view object and then sort the view object, all on the same property just in case) or implicitly assume the repository sorts and accidentally remove that somehwere along the way, breaking the UI.

'Unit test' is just a name

In my opinion, unit tests should mostly not hit the database. You build an application so that every piece of code that needs data from a source does this with a repository, and that repository is injected as a dependency. This allows for easy mocking and all the TDD-goodness you want. But in the end you want to make sure that your repositories perform their duties and if the easiest way to do that is hit a database, well, so be it. I've long let go of the notion that 'unit tests should not touch the database' and learned that there are very real reasons to do this. But only if you can do this automatically and repeatedly. And weather we call such a test a 'unit test' or an 'integration test' is moot.

  • 3
    Unit tests and integration tests have different purposes. The names for these tests are not merely decorative; they are also descriptive. – Robert Harvey Dec 31 '14 at 6:26
9
  1. Don't test trivial or obvious repository methods.

    If the methods are trivial CRUD operations, all you're really testing is whether parameters are mapped correctly. If you have integration tests, such errors will become immediately apparent anyway.

    This is the same principle that applies to trivial properties, like this one:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

    You don't test it, because there's nothing to test. There's no validation or other logic in the property that needs to be verified.

  2. If you still want to test those methods...

    Mocks are the way to do it. Remember, these are Unit Tests. You don't test the database with unit tests; that's what Integration Tests are for.

More Information
The Full Stack, Part 3: Building a Repository using TDD (start watching at about 16 minutes in).

  • 3
    Sure, I understand this. Still, if it's TDD approach, I should not write any code if i don't have tests for this code first, right? – Thaven Dec 28 '14 at 21:24
  • 1
    @Thaven - there's a series of videos on youtube entitled "is tdd dead?". Watch them. They address a lot of interesting points, one of which is the notion that applying TDD at every level of your application isn't necessarily the best idea. "no code without a failing test" is too extreme a position, is one of the conclusions. – Jules Dec 28 '14 at 22:41
  • 2
    @Jules: What's the tl;dw? – Robert Harvey Dec 29 '14 at 0:13
  • 1
    @RobertHarvey It's hard to summarize, but the most important point was that treating TDD as a religion that must always be observed is a mistake. The choice to use it is part of a trade-off and you need to consider that (1) you may be able to work faster without it on some problems and (2) it may push you towards a more complex solution than you need, particularly if you find yourself using a lot of mocks. – Jules Dec 29 '14 at 12:39
  • 1
    +1 for point #1. Tests can be wrong, it's just that they're usually trivial. It's pointless to test a function whose correctness is more obvious than the test's. It's not like getting 100% code coverage gets you anywhere close to testing every possible execution of the program, so you might as well be smart about where you spend testing effort. – Doval Dec 29 '14 at 14:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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