caked bake
caked bake

Reputation: 327

How to write unit test for methods using nHibernate

I am trying to create application using .net mvc 4 and fluent nhibernate.

I have created ProductsFacade which is responsible for getting and inserting data to a database. Method GetProductsByPageAndCategory is used to get page of records from database. I want to write unit test which checks if pagination works well.

It's hard to do because pagination must be done in single QueryOver query. I can't write separate method only fetching data, mock it and write separate method for pagination. So I need to mock database. I use moq tool for mocking.

Maybe anyone could give some tips on how to do it? Or any other alternative how to solve my problem?

public class ProductFacade {
    //...

    public virtual IList<Product> GetProductsByPageAndCategory(
        string category,
        int pageNumber,
        int pageSize)
    {
        //IList<Product> products = ;
        var productsQuery = _session.QueryOver<Product>();
        if (category != null)
        {
            productsQuery.Where(p => p.Category == category);
        }

        IList<Product> products = productsQuery
            .OrderBy(p => p.Id)
            .Desc
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize)
            .List<Product>();

        return products;
    }

    //...
}

Upvotes: 1

Views: 3560

Answers (3)

Firo
Firo

Reputation: 30813

inmemory database is much less code than mocking, easier to understand and closer to the real thing. It also makes sure your mappings are correct so no extra load save tests needed.

//for all tests

static Configuration config;
static ISessionFactory testSessionFactory;

config = Fluently.Configure()
    .Database(SQLiteConfiguration.Standard.InMemory().ShowSql().FormatSql())
    .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<Foo>())  // add your mappings here
    .BuildConfiguration();

testSessionFactory = config.BuildSessionFactory();

// in test
public ProductTests()
{
    session = sf.OpenSession();
    new SchemaExport(config).Execute(true, true, false, session.Connection, null);
}

private void InjectSampleData(params object[] objects)
{
    foreach (var obj in objects)
    {
        session.Save(obj);
    }
    session.Flush();
    session.Clear();
}

public void TestThis()
{
    InjectSampleData(
        new Product() { ... },
        new Product() { ... },
        new Product() { ... },
        new Product() { ... },
    );

    var products = new ProductFacade(session).GetProductsByPageAndCategory(...);

    // assert products match expected subcollection of Injected Data
}

Upvotes: 2

Andrew Shepherd
Andrew Shepherd

Reputation: 45232

Here's my alternative - don't mock the database.

In our test setup, on every developer's machine there must be a database with a given name (eg "CMS_AutoTests"). When the tests run it interacts with this database.

The TearDown method that runs after each tests runs a stored procedure that clears every table, so each test starts with an empty database.

Upvotes: 2

MichaC
MichaC

Reputation: 13381

I also use moq for mocking NHibernate session, here is a very simple example of how to mock the NHibernate ISession and ISessionFactory.

    var mockSession = new Mock<ISession>();
    mockSession.Setup(r => r.Get<ExampleEntity>(It.IsAny<int>()))
        .Returns(new ExampleEntity());

    var mockSessionFactory = new Mock<ISessionFactory>();
    mockSessionFactory.Setup(r => r.OpenSession())
        .Returns(mockSession.Object);

    var sessionFactory = mockSessionFactory.Object;
    // inject mockSessionFactory.Object to your business layer...

    // code where ever sessionFactory is used...
    // OpenSession now returns the mocked session
    using (var session = sessionFactory.OpenSession())
    {
        //Get of type ExampleEntity will always return whatever you define in your mock
        var rs = session.Get<ExampleEntity>(1);
    }

To use mocking for your business objects, you'll have to design it in a way that you can manually construct it so that it uses your mocked factory.

Usually this is easy if you use injection with Unity for example. With unity the constructor of your Business class might take the session or the factory or whatever... In this case, within your unit test your can construct the target manually and pass your mock into it...

Upvotes: 2

Related Questions