【问题标题】:How to unit test C# events with xUnit如何使用 xUnit 对 C# 事件进行单元测试
【发布时间】:2019-12-12 20:25:22
【问题描述】:

我想对由依赖项引发的事件是否被被测类订阅的事件进行单元测试。 为了设置上下文,我有以下接口和类。

ITestedService.cs

public interface ITestedService
{
  Task Start();
  Task Stop();
}

IDependency.cs

public interface IDependency
{
    event EventHandler<SoAndSoEventArgs> SomethingHappened;
    Task Start();
    Task Stop();
}

ISSecondDependency

public interface ISecondDependency
{
    Task DoYourJob(SoAndSo soAndSo);
}

TestedService.cs

public class TestedService : ITestedService
{
    readonly IDependency m_dependency;
    readonly ISecondDependency m_secondDependency;

    public TestedService(
        IDependency dependency,
        ISecondDependency secondDependency)
    {
        m_dependency = dependency;
        m_secondDependency = secondDependency;
    }

    public async Task Start()
    {
        m_dependency.SomethingHappened +=  OnSomethingHanppened;
        await m_dependency.Start();
    }

    private async void OnSomethingHanppened(object sender, SoAndSoEventArgs args)
    {
        SoAndSo soAndSo = SoAndSoMapper.MapToDTO(args);
        await m_secondDependency.DoYourJob(soAndSo),
    }

}

在上述情况下,我想使用xUnitTestedService 类的Start() 方法进行单元测试。 我想知道我该怎么做:

  • 断言事件是否附加到处理程序。
  • 模拟被触发的事件IDependency.SomethingHappened
  • 验证OnSomethingHappened方法是否被执行
  • 验证是否调用了ISecondDependency.DoYourJob(soAndSo)

【问题讨论】:

  • AFAICT 您需要引发SomethingHappened 实例的SomethingHappened 事件。可能this 有帮助吗?它展示了如何使用m_dependencyMock.Raise
  • 那么,我 m_dependencyMock.Raise( 是该行为的一部分,然后 ryt? @ZevSpitz
  • 是的,我想是的。 (免责声明:我在起订量方面没有经验,或者在一般的嘲笑方面。)
  • 我宁愿把问题的单元测试部分变成答案。

标签: c# .net events xunit xunit.net


【解决方案1】:

根据this answerthis documentation 以及@ZevSpitz 在 cmets 的指导,我能够为 Start() 编写以下测试。 虽然我无法验证是否执行了相同的代码路径 OnSomethingHappened 还是其他调用 m_secondDependencyMock.DoYourJob(soAndSo) 的订阅。

TestedServiceTest.cs

public class TestedServiceTest
{
    readonly Mock<IDependency> m_dependencyMock;
    readonly Mock<ISecondDependency> m_secondDependencyMock;

    ITestedService testedService;

    public TestedServiceTest()
    {
        m_dependencyMock = new Mock<IDependency>();
        m_secondDependencyMock = new Mock<ISecondDependency>();
        testedService = new TestedService(m_dependencyMock.Object, m_secondDependencyMock.Object);
    }

    [Fact]
    public async Start_DependencyStartInvoked()
    {
        // Arrange
        m_dependencyMock.Setup(x=> x.Start()).Verifyable();

        // Act 
        await testedService.Start();

        // Assert
        //This tests if the IDependecy.Start is invoked once.
        m_dependencyMock.Verify(x=>x.Start(), Times.Once);
    }

    [Fact]
    public async Start_EventListenerAttached()
    {
        // Arrange
        m_dependencyMock.Setup(x=> x.Start()).Verifyable();
        m_dependencyMock.SetupAdd(m => m.SomethingHappened += (sender, args) => { });

        // Act 
        await testedService.Start();

        // Assert
        // The below together with SetupAdd above asserts if the TestedService.Start adds a new eventlistener
        // for IDependency.SomethingHappened
        m_dependencyMock.VerifyAdd(
            m => m.SomethingHappened += It.IsAny<EventHandler<SoAndSoEventArgs>>(), 
            Times.Exactly(1));
    }

    [Fact]
    public async Start_SomthingHappenedInvoked_HandlerExecuted()
    {
        // Arrange
        m_dependencyMock.Setup(x=> x.Start()).Verifyable();
        m_secondDependencyMock.Setup(x=> x.DoYourJob(It.IsAny<SoAndSo>())).Verifyable();

        // Act
        await testedService.Start();
        // This will fire the event SomethingHappened from m_dependencyMock.
        m_dependencyMock.Raise(m => m.SomethingHappened += null, new SoAndSoEventArgs());

        // Assert
        // Assertion to check if the handler does its job.
        m_secondDependencyMock.Verify(x=> x.DoYourJob(It.IsAny<SoAndSo>()), Times.Once);
    }
}

【讨论】:

    【解决方案2】:

    单元测试的目的可以是:

    1. 在你想要的输出中验证逻辑结果
    2. 验证是否进行了关键调用(我只会在我想确保其他开发人员不会错误地删除一段代码时才这样做,但通常会验证 是否拨打电话是不必要的,甚至更糟的是, 不必要的维护工作)

    话虽如此,您无需测试语言的内部结构。例如,在这种情况下,您无需验证注册事件时是否会调用注册的方法。这是语言的工作。这是由语言测试的。

    因此,您确认 Start 方法执行了您预期的调用。顺便说一句,正如我上面提到的,只有在有理由这样做时才有意义,例如上面的目的 2。 现在您知道 OnSomethingHappened 将被触发。语言保证了这一点。 您要测试的是 OnSomethingHappened 中的实际实现。为此,您需要通过使其可访问(访问修饰符 private 不起作用)并使其依赖项也可模拟(SoAndSoMapper 不可模拟)来使该方法更具可测试性。

    注意:单元测试更多的是使代码可测试的活动,而不是弄清楚如何编写测试的活动。如果编写测试很困难,这可能表明代码不容易测试。

            public class TestedService
        {
            readonly IDependency m_dependency;
            readonly ISomethingDoer m_somethingDoer;
    
            public TestedService(
                IDependency dependency,
                ISomethingDoer somethingDoer)
            {
                m_dependency = dependency;
                m_somethingDoer = somethingDoer;
            }
    
            public async Task Start()
            {
                m_dependency.SomethingHappened += m_somethingDoer.OnSomethingHanppened;
                await m_dependency.Start();
            }
        }
    
        interface ISomethingDoer
        {
           Task OnSomethingHanppened(object sender, SoAndSoEventArgs args);
        }
        class SomethingDoer : ISomethingDoer
        {
            readonly ISecondDependency m_secondDependency;
            readonly ISoAndSoMapper m_soAndSoMapper;
            public SomethingDoer(ISecondDependency secondDependency, ISoAndSoMapper soAndSoMapper)
            {
               m_secondDependency = secondDependency;
    m_soAndSoMapper = soAndSoMapper;
            }
    
            public async Task OnSomethingHanppened(object sender, SoAndSoEventArgs args)
            {
                SoAndSo soAndSo = m_soAndSoMapper.MapToDTO(args);
                await m_secondDependency.DoYourJob(soAndSo),
            }
        }
    

    现在您可以通过为 SomethingDoer 创建一个测试类、模拟它的依赖项并验证例如给定 soAndSoMapper 模拟返回某个值,使用该值调用 secondDependency 来测试 OnSomethingHappened 的功能。虽然再一次, OnSomethingHappened 并没有做太多。因此,是否要对此进行测试是有争议的。

    【讨论】:

    • 是的,2 是我的目的。我想确保代码没有被删除。我不是来测试框架或语言的。我试图让我的下一个开发人员知道代码是有目的的,不能被删除。
    • 无论如何,投票赞成“OnSomethingHappened 并没有多大作用。因此,是否要测试这个是有争议的”。
    猜你喜欢
    • 1970-01-01
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 2021-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-19
    相关资源
    最近更新 更多