How to unit test C# events with xUnit
Asked Answered
A

2

6

I want to unit test if an event raised by a dependency being subscribed by a class under test. To set the context, I have the below interfaces and classes.

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

}

With the above context, I want to Unit test Start() method of the TestedService class using xUnit. I want to know how I can:

  • Assert if the event is attached to a handler.
  • Simulate the event IDependency.SomethingHappened being fired.
  • Verify if the OnSomethingHappened method is executed
  • Verify if the ISecondDependency.DoYourJob(soAndSo) is called.
Authenticity answered 12/12, 2019 at 20:25 Comment(4)
AFAICT you need to raise the SomethingHappened event of the m_dependencyMock instance. Might this help? It shows how to use m_dependencyMock.Raise.Mottle
So, I m_dependencyMock.Raise( is part of the act then ryt? @ZevSpitzAuthenticity
Yes, I think so. (Disclaimer: I have no experience in Moq, or in mocking in general.)Mottle
I would rather make the unit testing part of the question into an answer.Authenticity
A
7

From this answer, this documentation and from the guidance by @ZevSpitz in comments I was able to write the below tests for Start(). Though I couldn't verify if the same code path OnSomethingHappened got executed or was it some other subscription which calls 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);
    }
}

Authenticity answered 12/12, 2019 at 21:17 Comment(0)
B
2

The purpose of unit testing can be:

  1. Verify logic results in the output you want
  2. Verify crucial calls are made (I would only do if I want to make sure another developer does not remove a piece of code by mistake but in general verifying whether some call is made is not necessary and even worse, makes unnecessary maintainability work)

Having said that, you do not need to test the internals of the language. For example in this case you do not need to verify that when you register an event, that the method registered will be called. It is the job of the language to do that. That is tested by the language.

So you verified that the Start method does the calls that you expected. This by the way, as I mentioned above, only makes sense to do if there is a reason to do so such as purpose number 2 above. Now you know the OnSomethingHappened is going to be triggered. The language guarantees that. What you want to test is the actual implementation within OnSomethingHappened. For this, you need to make this method more testable by making it reachable (access modifier private is not going to work) and by making it's dependencies also mockable (SoAndSoMapper is not mockable).

Note: Unit testing is more of an activity of making code testable rather than the activity of figuring out how to write the test. If writing the test is difficult, that can be a sign that code is not easily testable.

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

Now you can test what OnSomethingHappened does by creating a test class for SomethingDoer, mocking it's dependencies and verifying for example that given soAndSoMapper mock returns some value, the secondDependency is called with that value. Although once again, OnSomethingHappened doesn't do much. Therefore it is arguable whether you want to test this.

Backbreaker answered 12/12, 2019 at 21:14 Comment(2)
Yes, 2 is my purpose. I want to make sure the code is not removed. I'm not here to test the framework or the language. I'm trying to let my next developer know that the code is there for a purpose and not to be removed.Authenticity
Anyway the vote is for "OnSomethingHappened doesn't do much. Therefore it is arguable whether you want to test this".Authenticity

© 2022 - 2024 — McMap. All rights reserved.