我正在为使用System.Data.Linq DataConext对象从数据库获取实体表的存储库编写单元测试。这是代码:
public class ForumRepository : IForumRepository
{
protected Table<Forum> DataTable;
IDataContextWrapper DataContext;
public ForumRepository(IDataContextWrapper DataContext)
{
DataTable = DataContext.GetTable<Forum>();
}
public Forum GetForumById(int id)
{
try
{
return DataTable.Single(f => f.tblForumID.Equals(id));
}
catch(Exception e)
{
return null;
}
}
这是包装器实现:
public class DataContextWrapper<T> : IDataContextWrapper where T : EpixForumDataContext, new()
{
private readonly T db;
public DataContextWrapper()
{
var t = typeof(T);
db = (T)Activator.CreateInstance(t);
}
public DataContextWrapper(string connectionString)
{
var t = typeof(T);
db = (T)Activator.CreateInstance(t, connectionString);
}
public Table<TableName> GetTable<TableName>() where TableName : class
{
return (Table<TableName>)db.GetTable(typeof(TableName));
}
我想测试存储库方法。
public class UnitTest1
{
[TestMethod]
public void Can_Get_Forum_ById()
{
//arrange
Forum dummyForum = new Forum() { tblForumID = 1};
Mock<ITable<Forum>> tableMock = new Mock<ITable<Forum>>();
tableMock.Object.Attach(dummyForum);
Mock<IDataContextWrapper> mock = new Mock<IDataContextWrapper>();
mock.Setup(m => m.GetTable<Forum>()).Returns(tableMock.Object) ;
//act
ForumRepository repos = new ForumRepository(mock.Object);
Forum resultForum = repos.GetForumById(1);
//assert
Assert.AreEqual(resultForum.tblForumID, 1);
论坛是自动生成的课程。我想设置Table的论坛,这样当我在ContextWrapper上做一个GetTable时,我得到了论坛表。我不知道Table.Attach是否会将论坛附加到桌面上。当我运行测试时,它说
'类型为mock必须是接口或抽象类或非密封 类。
我弄错了吗?
答案 0 :(得分:3)
我看到你正在尝试做的几个问题。
发布的代码无法编译
IDataContextWrapper.GetTable
会返回Table<T>
,因此您无法将其设置为返回模拟的ITable<T>
。 ITable
不一个Table
,反之亦然。这引出了我的下一点:
IDataContextWrapper.GetTable
应该返回ITable<T>
,而不是Table<T>
。
这将让你模拟返回结果,因为Table<T>
是sealed(Moq不能模拟密封的类,这可能是你得到你提到的错误的原因)。它也是program to interfaces而不是混凝土的好设计。
您不应期望拨打Attach
来执行任何操作
你试图在一个模拟界面上调用一个方法,就像你期望它表现得好像已经实现了它一样。 mock的方法只会按照你告诉他们的方式执行,因此在这种情况下(使用Loose behavior)它将对该调用不执行任何操作。相反,你应该设置你希望表格做什么,但这导致我:
您无法设置对Single
的调用,因为它是一种扩展方法
Moq doesn't support setting up extension methods,因为它们是静态方法。但是,您可以设置GetEnumerator
的呼叫,这是Single
呼叫的呼叫。你需要mock the IQueryable<T>
members,因为那是Single
真正要达到的目的。
因此,在解决上述第1点后,您的测试最终应如下所示:
[Test]
public void Can_Get_Forum_ById()
{
// arrange
Forum dummyForum = new Forum { tblForumID = 1 };
IQueryable<Forum> forums = new List<Forum> { dummyForum }.AsQueryable();
Mock<ITable<Forum>> tableMock = new Mock<ITable<Forum>>();
tableMock.Setup(p => p.GetEnumerator()).Returns(forums.GetEnumerator());
tableMock.Setup(r => r.Provider).Returns(forums.Provider);
tableMock.Setup(r => r.ElementType).Returns(forums.ElementType);
tableMock.Setup(r => r.Expression).Returns(forums.Expression);
Mock<IDataContextWrapper> mock = new Mock<IDataContextWrapper>();
mock.Setup(m => m.GetTable<Forum>()).Returns(tableMock.Object);
// act
ForumRepository repos = new ForumRepository(mock.Object);
Forum resultForum = repos.GetForumById(1);
// assert
Assert.AreEqual(resultForum.tblForumID, 1);
}
请注意,这是一个很好的,不是很好的测试。您可以通过调用Single
来取代对First
的调用,但它仍会通过,但显然一般都是错误的。您至少应该添加此测试的否定值,即如果Id不匹配则不返回任何对象。
答案 1 :(得分:0)
尝试这样的事情
Forum dummyForum = new Forum() { tblForumID = 1};
Mock<ITable> tableMock = new Mock<ITable>();
tableMock.Object.Attach(dummyForum);
Mock<IDataContextWrapper> contextMock = new Mock<IDataContextWrapper>();
contextMock .Setup(m => m.GetTable<Forum>()).Returns((ITable<Forum>)tableMock.Object) ;
//act
ForumRepository repos = new ForumRepository(mock.Object);
Forum resultForum = repos.GetForumById(1);
//assert
Assert.AreEqual(resultForum.tblForumID, 1);