简体   繁体   中英

Unit test for void method with Interface as parameter

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}");
    }
}

Edit: using MSTest for Unit testing

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 (ie 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.

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 NSubstitute library for that.
// Don't forget to add using NSubstitute

// Arrange
var userNotification = new UserNotification();
var workContext = Substitute.For<IWorkContext>();
workContext.Name = "testName";
workContext.Dept = "testDept";

Now you act, and invoke your method:

// Act
Action act = () => userNotification.Execute(workContext);

And lastly we assert. I highly recommend FluentAssertations library for asserting.

// Assert
act.Should().Throw<InvalidWorkException>()
.WithMessage($"some message:{workContext.Name} and :{workContext.Dept}");

Navigate to View > Test Explorer and run your tests, you should see something similar to this:

在此处输入图像描述

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!

If you want to use nunit the documentation with example is pretty easy to follow, link below.

Nunit documentation

And I think all other unit test framework have something similar to this.

[Test]
public void Execute_WhenCalled_ThrowArgumentException()
{
    //Initialize an instance of IWorkContext 
    var iwc = new WorkContext();
    //or use a Mock object, later on in assert use
    //userNotification.Execute(iwc.Object) 
    var iwc = new Mock<IWorkContext>();

    var userNotification = new UserNotification();

    Assert.Throws(typeof(InvalidWorkException), () =>
    {     
        userNotification.Execute(iwc) 
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM