如何使用 xUnit 对 C# 事件进行单元测试

Jin*_*ter 6 .net c# events xunit xunit.net

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

ITestedService.cs

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

Run Code Online (Sandbox Code Playgroud)

IDependency.cs

public interface IDependency
{
    event EventHandler<SoAndSoEventArgs> SomethingHappened;
    Task Start();
    Task Stop();
}
Run Code Online (Sandbox Code Playgroud)

ISecondDependency

public interface ISecondDependency
{
    Task DoYourJob(SoAndSo soAndSo);
}
Run Code Online (Sandbox Code Playgroud)

测试服务.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),
    }

}

Run Code Online (Sandbox Code Playgroud)

有了上述上下文,我想Start()使用. 我想知道我怎样才能:TestedServicexUnit

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

Jin*_*ter 7

根据这个答案本文档以及@ZevSpitz 在评论中的指导,我能够为 Start() 编写以下测试。虽然我无法验证是否OnSomethingHappened执行了相同的代码路径,或者是否是其他调用的订阅m_secondDependencyMock.DoYourJob(soAndSo)

测试服务测试.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);
    }
}

Run Code Online (Sandbox Code Playgroud)