2013-09-29 71 views
1

我正在尝试使用.net mvc 4和流利的nhibernate来创建应用程序。如何为使用nHibernate的方法编写单元测试

我创建了ProductsFacade,它负责获取数据并将数据插入数据库。 方法GetProductsByPageAndCategory用于从数据库中获取记录页面。我想编写单元测试,检查分页是否工作正常。

这很难做,因为分页必须在单个QueryOver查询中完成。我不能编写单独的方法来获取数据,模拟它并为分页编写单独的方法。所以我需要模拟数据库。我使用moq工具来嘲笑。

也许任何人都可以提供一些关于如何做到这一点的提示?或者其他的选择如何解决我的问题?

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; 
    } 

    //... 
} 
+0

是的,嘲笑'session'变量是解决方案。然后,您可以验证对后续流畅方法的调用是否正确完成。有什么问题? – BartoszKP

回答

2

我也用起订量为嘲讽NHibernate的会议上,这里是如何嘲笑NHibernate的ISessionISessionFactory一个很简单的例子。

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); 
    } 

要使用嘲笑你的业务对象,你就必须在某种程度上,你可以手动构造它,以便它使用您的嘲笑工厂设计它。

通常这很容易,如果你使用Unity注入例如。有了统一,你的商务类的构造函数可能需要会话或工厂或其他...在这种情况下,在你的单元测试中,你可以手动构建目标并将其传递给它...

+0

我使用Moq嘲笑会话,并使用公开'IQueryable '的IDbService,这样我就可以控制包含NHibernate会话的单元测试控制器,甚至在您的案例中使用您的外观。我刚刚从@OdeToCode那里得到了这个想法。但是有些人不喜欢这种做事的方式 – Rippo

1

这是我的替代方案 - 不要模拟数据库。

在我们的测试设置中,在每个开发人员的机器上必须有一个具有给定名称的数据库(例如“CMS_AutoTests”)。当测试运行时,它与这个数据库进行交互。

TearDown方法在每个测试运行后运行一个存储过程来清除每个表,因此每个测试都从一个空数据库开始。

+1

我们几乎做了同样的事情,我认为这些测试更好,因为它们更接近集成测试。 –

+0

@ColeW这实际上是集成测试......但不是单元测试。单元测试不应该关心数据,你只需测试小单元(一段代码),而不想从数据库中读取/写入任何数据。所以对于propper单元测试,你必须模拟你的数据库。 对于集成测试,当然,你将不得不为每个测试集建立一个测试数据库... – MichaC

+1

@Ela我同意你的说法,但是如果你正在进行集成测试,那么谁需要单元测试呢?让我们面对它,无论如何你都无法绕过集成测试,所以为什么写2个测试基本上做同样的事情。我开始采用相同的方式,最终没有嘲笑数据库。 –

2

内存数据库比嘲笑少得多的代码,更容易理解和接近真实的东西。它还确保您的映射是正确的,因此不需要额外的负载保存测试。

//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 
} 
相关问题