Home > Back-end >  Unit test for void method with Interface as parameter
Unit test for void method with Interface as parameter

Time:03-02

New to Unit testing, I have below sample code and I want to create a unit test for this , Please suggest what should i do to create a unit test for this ? any link or pointers would be helpful to start

public class UserNotification : Work
{
    public override void Execute(IWorkContext iwc)
    {
        throw new InvalidWorkException($"some message:{iwc.Name} and :{iwc.Dept}");
    }
}

CodePudding user response:

First, you need a test project alongside with your regular project. You can pick from these three:

  • MSTest
  • nUnit
  • xUnit

All of these should have a project template in VS2022.

xUnit is a popular one, so let's pick that. The usual naming convention for test projects is YourProject.Tests. Rename UnitTest1.cs class to UserNotificationTests.cs.

As simple as it gets, you can now start writing your tests. In xUnit, a method with [Fact] attribute is a test method.

using Xunit;

namespace MyProject.Tests
{
    public class UserNotificationTests
    {
        [Fact]
        public void Execute_Should_Throw_InvalidWorkException_With_Message()
        {

        }
    }
}

Don't think these methods as the methods in the code, naming should be close to English sentences and should reveal the intent as a regular sentence.

Classic approach to unit testing has three phases:

  • Arrange: Take instances of your objects, set your expected output, mock dependencies, make them ready.
  • Act: Call the actual action you want to test.
  • Assert: Check if how your actual output relates to your expected output.

Let's start with arranging.

  • We need a new instance of UserNotification class so we can call Execute().
  • We need any dummy IWorkContext object so we can pass it. We'll use enter image description here

    Congratulations, you wrote your first unit test. Here's the final version of your test code:

    using FluentAssertions;
    using NSubstitute;
    using System;
    using Xunit;
    
    namespace MyProject.Tests
    {
        public class UserNotificationTests
        {
            [Fact]
            public void Execute_Should_Throw_InvalidWorkException_With_Message()
            {
                // Arrange
                var userNotification = new UserNotification();
                var workContext = Substitute.For<IWorkContext>();
                workContext.Name = "testName";
                workContext.Dept = "testDept";
    
                // Act
                Action act = () => userNotification.Execute(workContext);
    
                // Assert
                act.Should().Throw<InvalidWorkException>()
                    .WithMessage($"some message:{workContext.Name} and :{workContext.Dept}");
            }
        }
    
        public class UserNotification : Work
        {
            public override void Execute(IWorkContext iwc)
            {
                throw new InvalidWorkException($"some message:{iwc.Name} and :{iwc.Dept}");
            }
        }
    
        public abstract class Work
        {
            public virtual void Execute(IWorkContext iwc) { }
        }
    
        public interface IWorkContext 
        {
            public string Name { get; set; }
            public string Dept { get; set; }
        }
    
        public class InvalidWorkException : System.Exception
        {
            public InvalidWorkException() { }
            public InvalidWorkException(string message) : base(message) { }
            public InvalidWorkException(string message, System.Exception inner) : base(message, inner) { }
            protected InvalidWorkException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
        }
    }
    

    Writing tests feels a lot different than writing regular code. But in time you'll get the hang of it. How to mock, how to act, how to assert, these may vary depending on what you are testing. The main point is to isolate the main thing you want to unit test, and mock the rest.

    Good luck!

    CodePudding user response:

    Because your title mentions specifically that you're trying to test a method with a void return type; I infer that you've already been testing methods with actual return values, and therefore that you already have a test project and know how to run a test once it is written. If not; the answer written by Mithgroth is a good explanation on how to get started on testing in general.


    Your test is defined by the behavior that you wish to test. Your snippet has no behavior, which makes it hard to give you a concrete answer.

    I've opted to rewrite your example:

    public class UserNotification : Work
    {
        public override void Execute(IWorkContext iwc)
        {
            var splines = iwc.GetSplines();
    
            iwc.Reticulate(splines);
        }
    }
    

    Now we have some behavior that we want to test. The test goal is to answer the following question:

    When calling Execute, does UserNotification fetch the needed splines and reticulate them?

    When unit testing, you want to mock all other things. In this case, the IWorkContext is an external dependency, so it should be mocked. Mocking the work context allows us to easily configure the mock to help with the testing. When we run the test, we will pass an IWorkContext object which acts as a spy. In essence, this mocked object will:

    • ... have been set up to return a very specific set of splines, one that we chose for the test's purpose.
    • ... secretly record any calls made to the Reticulate method, and tracks the parameters that were passed into it.

    Before we get into the nitty gritty on how to mock, we can already outline how our test is going to go:

    [Test]
    public void ReticulatesTheContextSplines()
    {
        // Arrange
        IWorkContext mockedContext = ...; // This comes later
        UserNotification userNotification = new UserNotification();
    
        // Act
        userNotification.Execute(mockedContext);
    
        // Assert
        // Confirm that Reticulate() was called
        // Confirm that Reticulate() was given the result from `GetSplines()`
         
    }
    

    There's your basic unit test. All that's left is to create our mock.

    You can write this yourself if you want. Simply create a new class that implements IWorkContext, and give it some more public properties/methods to help you keep track of things. A very simple example would be:

    public class MockedWorkContext : IWorkContext
    {
        // Allows the test to set the returned result
        public IEnumerable<Spline> Splines { get; set; }
    
        // History of arguments used for calls made to Reticulate. 
        // Each call will add an entry to the list.
        public List<IEnumerable<Spline>> ReticulateArguments { get; private set; } = new List<IEnumerable<Spline>>();
    
        public IEnumerable<Spline> GetSplines()
        {
            // Returns the preset splines that the test configured
            return this.Splines;
        }
    
        // Mocked implementation of Reticulate()
        public void Reticulate(IEnumerable<Spline> splines)
        {
            // Does nothing except record what you passed into it
            this.ReticulateArguments.Add(splines);
        }
    }
    

    This is a very simplified implementation, but it gets the job done. The test will now look like this:

    [Test]
    public void ReticulatesTheContextSplines()
    {
        // Arrange
        IEnumerable<Spline> splines = new List<Spline>() { new Spline(), new Spline() }; // Just create some items here, it's random test data.
        IWorkContext mockedContext = new MockedWorkContext();
        mockedContext.Splines = splines;
    
        UserNotification userNotification = new UserNotification();
    
        // Act
        userNotification.Execute(mockedContext);
    
        // Assert - Confirm that Reticulate() was called
        mockedContext.ReticulateArguments.Should().HaveCount(1);
    
        // Confirm that Reticulate() was given the result from `GetSplines()`
        mockedContext.ReticulateArguments[0].Should().BeEquivalentTo(splines);
         
    }
    

    This test now exactly tests the behavior of your method. It uses the mocked context as a spy to report on what your unit under test (i.e. UserNotification) does with the context that you pass into it.

    Note that I am using FluentAssertions here, as I find it the most easily readable syntax. Feel free to use your own assertion logic.

    While you can write your own mocks; there are mocking libraries that help cut down on the boilerplating. Moq and NSubstitute are the two biggest favorites as far as I'm aware. I personally prefer NSubstitute's syntax; but both get the job done equally well.

  • Related