如何使用xUnit单元测试事件

问题描述 投票:0回答:2

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

ITestedService.cs

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

IDependency.cs

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

ISecondDependency

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),
    }

}

在上述情况下,我想使用Start()TestedService类的xUnit方法进行单元测试。我想知道如何:

  • 如果事件附加到处理程序,则断言。
  • 模拟事件IDependency.SomethingHappened被触发。
  • 验证是否执行OnSomethingHappened方法
  • 验证是否调用ISecondDependency.DoYourJob(soAndSo)
c# .net events xunit xunit.net
2个回答
1
投票

单元测试的目的可以是:

  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并没有做太多事情。因此,是否要进行测试是有争议的。


0
投票

this answerthis documentation以及@ZevSpitz的注释中,我能够为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);
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.