7

I am trying to learn how to mock my generic repository so i can unit test all my services.

Im using NHibernate Fluent to handle data access and a Ninject for dependency (I'm not interested in testing that)

My repository interface looks like:

public interface IRepository<TEntity>  where TEntity : class 
{
    IQueryable<TEntity> GetAll();
    TEntity Get(int key);
    void Insert(TEntity entity);
    void Update(TEntity entity);
    void Delete(int id);
}

And the actual repository looks like:

public class GenerRepository<TEntity> : IRepository<TEntity>where TEntity : Entity
{
    protected ISession Session{get { return NHibernateHelper.OpenSession(); }}
    public IQueryable<TEntity> GetAll(){return Session.Query<TEntity>();}
    public TEntity Get(int key){return Session.Get<TEntity>(key);}
    public void Insert(TEntity entity){Session.Save(entity);}
    public void Update(TEntity entity){Session.Update(entity);}
    public void Delete(int id){Session.Delete(Session.Load<TEntity>(id));}
}

All my services do the following take the created repository in and use it.

I've read so many articles on how to do this but none are simple or well explained. So any advice between creating a test generic repository or even mocking it. I would also be interested in creating a in memory database but how do i set the configuration up for fluent nhibernate in my test project without editing code in my real project?

Is it possible just to make the generic repository hit a list of Tentity rather than the database or in memory database.

Thanks for reading and look forward to the advice.

5
  • It is possible. If you go that way, be aware that LINQ provider is way more limited than LINQ 2 Objects, which means you can end up with your unit tests passing against the List<T>, and the application crashing when querying against the database. Commented Dec 21, 2013 at 16:08
  • Ahh thanks for that, So how would you advice me to test this? Are you able to provide me an example? Commented Dec 21, 2013 at 16:09
  • 2
    Maybe it's against the 'purity' of unit testing (since it's going more into a territory of simple integration tests), but I'm usually using in-memory SQLite database. With latest NHibernate and drivers I didn't have one NHibernate LINQ query that would fail on SQLite (while working on SQL Server). Commented Dec 21, 2013 at 16:11
  • So would you be able to help me and give some advice on how to point nhibernate to a in memory database in my test project but allow the real project to hit a real database even a simple example would be more than amazing! Commented Dec 21, 2013 at 16:12
  • Well, there's (admittedly a little bit outdated) link in my last comment, but it should get you going. Generally, you build your session factory with different database driver using the same mappings, it works fine with Fluent NHibernate. Unfortunately I don't have my regular set up available right now... :) Commented Dec 21, 2013 at 16:14

3 Answers 3

6

I have to agree with Radim, that unit testing nhibernate code by mocking the nhibernate functionality in most cases in not what you want to do.

Unless you want to test complex business logic which is based on data you retrieve via nhibernate, then this is perfectly fine.

But to test if your mappings, data retrieval and persistence works fine, you have to test against a real database.

If you target MSSQL Server, I would not use another type of database. Instead there is SQL Express which has all features of the real server. MSSQL Express can optionally be installed with local DB. This will allow you to load mdf files via connection string which will more or less instantiate an instance of MSSQL Server...

I used that for integration testing and it works really nice.

  1. Create a data base file in your unit test project
  2. Depending on your model (code first/db first) let nhibernate create the scheme, otherwise simple populate the scheme into that database file
  3. Add the file to the deployment items of your test settings so that the file gets copied to the test target directory
  4. Generate a connection string which uses the copied database file. Example connection string: Data Source=(LocalDB)\v11.0;AttachDbFileName=[whateverthepathis]\DatabaseFileName.mdf;InitialCatalog=DatabaseName;Integrated Security=True;MultipleActiveResultSets=True
  5. Run your tests

This way your tests will run with an empty database every time and you will have reproduceable integration tests without the need of a real server where you would have to create a DB or reset it everytime...

Sign up to request clarification or add additional context in comments.

Comments

5

There are couple of ways to achieve this,

  1. Use a real database for testing using scripts to setup and revert the database, but with this approach it would take time and effort to create and maintain these scripts when there are changes to the database

  2. Use a real database, and use transaction scope for testing (starting the transaction persist, and do the test and once all is done only rolling back the transaction), this is a really good approach and I use this for a large scale project. However one problem with this is it takes a lot of time to run tests (I have around 3500 tests and it takes total of 40 minutes to run them all)

  3. Use a fake repositories (having an internal list of entities) for business logic test and use actually repositories to verify the mappings. This approach would require additional effort to create and maintain fake repositories. The same tests executed on actual repositories can be executed on fake repositories to verify fakes are working. With this approach test execution would be faster.

Comments

5

My answer should/could be a comment, maybe. Because I would like to tell you: do not do it. Do not waste your time to create a fake of the data to be returned from persistence. And do not invest your time to: take the data from a client and put them into some virtual DB in memory.

You need to be sure, that your services (consuming repository) can really serialize/render the real data. And deserialize/persist the changed. And that would really require a real data.

Rather spend some time to create scripts, which will populate the test data. The data which you can expect in your tests: when doing Business validation, Service data serialization...

Also take a look here: Ayende: NHibernate Unit Testing. An extract:

When using NHibernate we generally want to test only three things, that properties are persisted, that cascade works as expected and that queries return the correct result. In order to do all of those, we generally have to talk to a real database, trying to fake any of those at this level is futile and going to be very complicated.

A note: some time ago, we used to wrap all the tests in Transaction Begin() and Rollback(). Which was looking good. But we realized, that lot of stuff - because of missing Flush() call - was not tested all the way down (e.g. setting not-null).

3 Comments

How does the sound, I use Ninject to bind the ISessioNFactory object within my class to an NHibernateHelper method which either creates one or uses one already made (this object is static) then in my test i send in my fake ISessionFactory which is mentioned in your link how to create?
The point is, that we should care about the Repository (not static Session Factory). Let's say, that we need to retrieve some data with GetAll() - the data which is not possible to place in DB with tests scripts. Then we should profit from the IoC. We will inherit new class TestRepository and in there we will change the implementation of the GetAll(). We will teach Ninject to use this implementation (or pass it via public setter) for this specific test. But for usual tests, we will use the "real" implementation.
@RadimKöhler Using In Memory Database is not a fake test.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.