【问题标题】:Write Moq Unit Test for internal method in the same class using Autofac使用 Autofac 为同一类中的内部方法编写 Moq 单元测试
【发布时间】:2014-12-30 01:23:00
【问题描述】:

我正在同一个类中尝试模拟内部方法。但是我的模拟失败了。

这是我的代码。

界面

public interface IStudentService
{
    int GetRank(int studentId);
    IList<Subject> GetSubjects(int studentId);
}

实施

public class StudentService : IStudentService
{
    private readonly IStudentRepository _studentRepository;
    private readonly ISubjectRepository _subjectRepository;

    public StudentService(IStudentRepository studentRepository, ISubjectRepository subjectRepository)
    {
        _studentRepository = studentRepository;
        _subjectRepository = subjectRepository;
    }

    public int GetRank(int studentId)
    {
        IList<Subject> subjects = GetSubjects(studentId);

        int rank = 0;
        //
        //Calculate Rank
        //
        return rank;
    }

    public virtual IList<Subject> GetSubjects(int studentId)
    {
        return _subjectRepository.GetAll(studentId);
    }
}

单元测试

[TestFixture]
public class StudentServiceTest
{
    [SetUp]
    public void Setup()
    {

    }

    [TearDown]
    public void TearDown()
    {

    }

    [Test]
    public void GetRankTest()
    {
        using (var mock = AutoMock.GetStrict())
        {
            var mockStudentService = new Mock<IStudentService>();
            mockStudentService.Setup(x => x.GetSubjects(1)).Returns(new ServiceResponse<SystemUser>(new List<Subject>{ new AccounProfile(), new AccounProfile()}));
            mock.Provide(mockStudentService.Object);

            var component = mock.Create<StudentService>();
            int rank = component.GetRank(1);
            mockStudentService.VerifyAll();

            Assert.AreEqual(1, rank, "GetRank method fails");
        }
    }
}

当我调试代码时,它不是在模拟 GetSubjects 方法。它实际上进入了那个方法。我正在使用 Nunit、Moq 和 Autofac 编写单元测试。

提前致谢!

【问题讨论】:

  • 既然可以轻松地模拟存储库,为什么还要尝试模拟内部方法?吉米做对了:)
  • 如果 GetSubject 方法有多个存储库方法,那么我必须全部模拟它们。此外,如果 GetSubjects 方法有来自同一类的另一个方法,我也必须模拟它们。因此,除了我正在测试的方法之外,我还必须模拟很多东西。 Autofac 是否支持部分模拟?
  • 首先,Autofac 是一个 DI 框架,根本与模拟无关。在您的示例中,您可以删除 Autofac 的所有痕迹,并且仍然可以使用模拟编写测试。事实上,我总是用尽可能少的额外框架编写测试:新建一个StudentService 实例并传入两个模拟存储库。是的,在测试一段代码时,您必须模拟外部依赖项,在您的情况下是存储库。如果您觉得发生的事情太多,也许这表明您的服务做得太多:然后将其拆分为更细化的服务!
  • ...建议:阅读更多关于什么是模拟的信息,例如这个帖子很好stackoverflow.com/questions/2665812/what-is-mocking

标签: c# unit-testing nunit moq autofac


【解决方案1】:

有两种解决方案。

1。部分模拟

在这种方法中,您创建正在测试的组件的模拟 (StudentService) 并告诉 Moq 模拟它的一些方法 (GetSubjects -- to-be-mocked 方法必须是virtual),同时将其他人(GetRank)委托给base implementation

设置mock.CallBase = true 指示Moq 将任何与显式Setup 调用不匹配的调用 委托给其基本实现。

// mockStudentService is not needed, we use partial mock
var service = mock.Create<StudentService>();
service.CallBase = true;
service.Setup(m => m.GetSubjects(1)).Returns(...);

var rank = service.GetRank(1);
// you don't need .VerifyAll call, you didn't not set any expectations on mock
Assert.AreEqual(1, rank, "GetRank method fails");

2。模拟内部服务(ISubjectRepository

部分模拟是为特殊情况保留的。你的情况比较常见。您的组件 (StudentService) 可以依赖已模拟的 ISubjectRepository 为其提供主题,而不是模拟自身:

using (var mock = AutoMock.GetStrict())
{
    var subjectRepositoryMock = new Mock<ISubjectRepository>();
    subjectRepositoryMock.Setup(x => x.GetSubjects(1)).Returns(...);
    mock.Provide(subjectRepositoryMock.Object);

    var component = mock.Create<StudentService>();
    int rank = component.GetRank(1);
    // verify is not needed once again

    Assert.AreEqual(1, rank, "GetRank method fails");
}

【讨论】:

  • 我将方法设为虚拟并尝试使用 CallBase = true 但它不起作用:(在您的第二个解决方案中,如果 GetSubject 方法有多个存储库方法,那么我必须全部模拟它们。进一步如果 GetSubjects 方法有来自同一类的另一个方法,我也必须模拟它们。因此,除了我正在测试的方法之外,我还必须模拟很多东西。解决方案是什么?
【解决方案2】:

此代码适用于。谢谢大家的支持

[TestFixture]
public class StudentServiceTest
{
    private Mock<StudentRepository> _studentRepositoryMock;
    private Mock<SubjectRepository> _subjectRepositoryMock;
    private Mock<StudentService> _studentServiceMock;

    [SetUp]
    public void Setup()
    {
        _studentRepositoryMock = new Mock<StudentService>(MockBehavior.Strict);
        _subjectRepositoryMock = new Mock<SubjectRepository>(MockBehavior.Strict);
        _studentServiceMock = new Mock<StudentService>(_studentRepositoryMock.Object, _subjectRepositoryMock.Object);
        _studentServiceMock.CallBase = true;
    }

    [TearDown]
    public void TearDown()
    {

    }

    [Test]
    public void GetRankTest()
    {
        _studentServiceMock.Setup(x => x.GetSubjects(1)).Returns(...);

        int rank = component.GetRank(1);
        _studentServiceMock.VerifyAll();

        Assert.AreEqual(1, rank, "GetRank method fails");
    }
}   

【讨论】:

    【解决方案3】:

    我猜你的GetSubjects 方法必须声明为虚拟的,否则无法模拟。

    public virtual IList<Subject> GetSubjects(int studentId)
    {
       // code here
    } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-03
      • 1970-01-01
      • 1970-01-01
      • 2017-02-03
      • 2013-07-08
      相关资源
      最近更新 更多