【问题标题】:C# + Mock service layer?C# + Mock 服务层?
【发布时间】:2010-10-17 11:06:54
【问题描述】:

我刚刚开始使用 Moq 进行单元测试/模拟,但遇到了一个问题..

我有一个名为“CustomerService”的服务层,其代码如下:

public interface ICustomerService
{
    Customer GetCustomerById(int id);
}

public class CustomerService : ICustomerService
{
    private IRepository<Customer> customerRepository;

    public CustomerService(IRepository<Customer> rep)
    {
        customerRepository = rep;
    }
    public Customer GetCustomerById(int id)
    {
        var customer = customerRepository.Get(x => x.CustomerId == id);

        if (customer == null)
            return null;

        return customer;
    }
}

我的存储库类是通用的,并且如下:

public interface IRepository<T> : IDisposable where T : class
    {
        T Get(Expression<Func<T, bool>> predicate);
    }

    public class Repository<T> : IRepository<T> where T : class
    {
        private ObjectContext context;
        private IObjectSet<T> objectSet;

        public Repository()
            : this(new demonEntities())
        {
        }

        public Repository(ObjectContext ctx)
        {
            context = ctx;
            objectSet = context.CreateObjectSet<T>();
        }

        public T Get(Expression<Func<T, bool>> predicate)
        {
            T entity = objectSet.Where<T>(predicate).FirstOrDefault();

            if (entity == null)
                return null;

            return objectSet.Where<T>(predicate).FirstOrDefault();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (context != null)
                {
                    context.Dispose();
                    context = null;
                }
            }
        }
    }

现在是我的问题。如何进行单元测试以检查我的 GetCustomerById 是否返回 null?

已经试过了:

[TestMethod]
public void GetCustomerTest()
{
    const int customerId = 5;

    var mock = new Mock<IRepository<Customer>>();
    mock.Setup(x => x.Get(z => z.CustomerId == customerId))
        .Returns(new Customer());

    var repository = mock.Object;
    var service = new CustomerService(repository);
    var result = service.GetCustomerById(customerId);

    Assert.IsNotNull(result);
}

运气不好……

【问题讨论】:

  • 通过传入一个模拟存储库。使用不在模拟存储库中的 id 调用。顺便说一句,这不是一个测试......
  • 已经试过了,没有运气...

标签: c# unit-testing mocking moq


【解决方案1】:

您需要将 Repository&lt;T&gt;.Get 方法设为虚拟,以便 Moq 可以覆盖它并返回您设置的值:

public virtual T Get(Expression<Func<T, bool>> predicate)

在你的测试中,改变

mock.Setup(x => x.Get(z => z.CustomerId == customerId))
        .Returns(new Customer());

mock.Setup(x => x.Get(It.IsAny<Expression<Func<Customer, bool>>>()))
        .Returns(new Customer());

表示为传入的任何Expression&lt;Func&lt;Customer, bool&gt;&gt; 返回一个新的Customer。理想情况下,您会测试一个特定的表达式,但根据对此SO question 的公认答案,Moq 不能这样做。

如果您想测试您的服务层没有对存储库返回的Customer 执行任何意外操作,而不是测试是否返回了 any Customer,您可以设置模拟Customer(确保将CustomerId 属性设为虚拟)并断言服务层返回的Customer 具有预期的属性。

[TestMethod]
public void GetCustomerTest()
{
    const int customerId = 5;

    var mockCustomer = new Mock<Customer>();

    mockCustomer.SetupGet(x => x.CustomerId)
        .Returns(customerId);

    var mock = new Mock<IRepository<Customer>>();

    mock.Setup(x => x.Get(It.IsAny<Expression<Func<Customer, bool>>>()))
        .Returns(mockCustomer.Object);

    var repository = mock.Object;
    var service = new CustomerService(repository);
    var result = service.GetCustomerById(customerId);

    Assert.AreEqual(customerId, result.CustomerId);
}

HTH

【讨论】:

  • 我在第 22 行遇到错误:“测试方法 MyApp.Test.Services.CustomerServiceTest.GetCustomerTest 抛出异常:System.ArgumentException:不可覆盖成员上的设置无效:x => x .CustomerId"
  • 检查确保CustomerId是虚拟的,那么应该没问题
  • 不知道Customer是EF生成的;在那种情况下,我不知道你能做到这一点,也许你的第一个 Assert.IsNotNull 是可以做到的最好的,并且应该使用上面的代码,虽然我不确定测试是否对你有用.可能是另一个问题的主题(如何模拟 EF 实体)。
  • 只是一个想法 - 我没有使用过 EF,所以我不知道,但我上面的评论是基于 EF 将重新生成此类,覆盖您所做的任何更改的假设。您仍然可以更改生成的类,但每次重新生成类时都必须进行相同的更改。我不知道是否有更好的方法来解决这个问题。如果您可以创建自己的实体类并使用属性来指示 EF 使用您的类而不是生成一个,那将是理想的,但我不知道这是否可能。
【解决方案2】:

您必须针对界面创建模拟。

然后您需要将类中的成员设置为模拟而不是实现者

然后您需要在为方法创建的模拟上调用 .Setup .. 记住使用方法链接来使其可验证。

在您的测试中运行该方法,然后调用 mock.Verify()

如果您需要,明天将添加代码。我在工作中有大量可以使用的代码示例。

【讨论】:

  • hmm.. 这几乎是我在 atm 所做的(如果你看我的帖子,我已经更新了一些代码)......
  • 从您的更新和其他 cmet 中我无能为力。虽然我在尝试类似的事情时放弃了,但对解决方案很感兴趣。
【解决方案3】:

你不能这样做的原因是因为一个 lambda x =&gt; x.CustomerId == id 不等于另一个 lambda x =&gt; x.CustomerId == id 所以 Moq 无法匹配它们。

但是,如果你有一个类,你的常用操作是:

public class CustomerQueries {
    public static Predicate<Customer> ById(int id) = x => x.CustomerId == id;
}

并在代码和测试中重复使用此 lambda,那么您的起订量应该可以顺利通过。您也可以在类的已知实例上使用方法,只要它是完全相同的委托,而不仅仅是类似的一个或另一个副本。

PS:您是否考虑过使用Predicate&lt;T&gt; 而不是Expression&lt;Func&lt;T, bool&gt;&gt;?它更易于阅读和理解其目的。

【讨论】:

    猜你喜欢
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-29
    • 2012-05-11
    • 2013-08-30
    • 2018-02-19
    相关资源
    最近更新 更多