Introduction to unit testing in C#

C#Unit testingTesting

Please contribute by voting. Thanks!
9
Once you've started working with unit testing, chances are that you never want to work without it again.

I find that the biggest benefit is that refactoring my code gets very easy. By running my unit tests I can be much more sure of that my code does what I intended, that any changes didn't cause any side effects and that I didn't reintroduce any regressions. Another important benefit is that you tend to structure your code better by making it testable (e.g. by separating interfaces from implementations).

A potential downside of unit testing is that the unit test themselves take some time to write (and can be quite tedious work), but this is often compensated for in the long run as refactoring the code takes much less time.

Enough about that, let's get starting on some code. This is the class that I thought we should write unit tests for:

    public class EmailSender : IEmailSender
    {
        private readonly IEmailProvider _emailProvider;
        private readonly IEmailValidator _emailValidator;

        public EmailSender(IEmailProvider emailProvider, IEmailValidator emailValidator)
        {
            _emailProvider = emailProvider;
            _emailValidator = emailValidator;
        }

        public bool SendEmail(string receiverEmailAddress, string subject, string body)
        {
            if (string.IsNullOrEmpty(receiverEmailAddress))
                throw new ArgumentException("You must specify an email address",
                    nameof(receiverEmailAddress));

            if (!_emailValidator.IsEmailValid(receiverEmailAddress))
                throw new ArgumentException("You must specify a valid email address",
                    nameof(receiverEmailAddress));

            if (string.IsNullOrEmpty(subject))
                throw new ArgumentException("You must specify a email subject",
                    nameof(subject));

            if (string.IsNullOrEmpty(body))
                throw new ArgumentException("You must specify a email body ",
                    nameof(body));

            return _emailProvider.SendEmail(receiverEmailAddress, subject, body);

        }
    }

As you see in the constructor, we are using dependency injection to inject our dependencies into the class. This way we can inject mock-objects or dummy implementations of those interfaces in our unit tests and thus do not need to test EmailProvider or EmailValidator while testing EmailSender (i.e. the test gets isolated to just test one unit of code).

You can read more about dependency injection and inversion of control here: http://www.codeaddiction.net/articles/10/dependency-injection-in-c---a-simple-introduction

A brief introduction to Microsoft Unit Test Framework

There are a lot of unit testing frameworks for C#, but Microsoft ships its own with Visual Studio. It may be a little limited in it's functionality but it gets you started really fast. This is how you get up and running:

1) In your solution, add a new project of type UnitTestProject

2) Rename the class (or delete the automatically created class and create a new one) called UnitTest1 to EmailSenderTest

3) Make sure that the following using-statement exists at the top of your file:

using Microsoft.VisualStudio.TestTools.UnitTesting;

4) Make sure that your class is decorated with the TestClass-attribute:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestProject1
{
    [TestClass]
    public class EmailSenderTest
    {

5) Create a new method in your EmailSenderTest-class called EmailSender_SendEmail_Successfully, the method should be decorated with the TestMethod-attribute:

        [TestMethod]
        public void EmailSender_SendEmail_Successfully()
        {
                //todo...
        }

6) Open the windows Test Explorer and hit the link Run All

As you will see, your project has 1 unit test - and it passed. Great! The only problem is that we're not testing anything yet...

A brief introduction to Moq

In order to test the method SendEmail we actually have to create an instance of EmailSender. The problem is that we need to inject instances of IEmailProvider and IEmailValidator in order to do that. This is where you could use a mocking-framework (other solutions exists, e.g. dummy implementations of the interfaces).

A mocking framework lets you create dummy instances from interfaces (by using Reflection) and specify how this instance should behave, e.g. we can say that if method X is called, it should return an instance of Y.

Let's grab Moq from the NuGet repository by open the Package Manager Console, selecting the test-project and executing the following command:

Install-Package Moq

You are now able to use Moq in your test project!

7) So in order to test the code we need to reference it. Right click the Reference-item under the test project, and add a reference to the main project and add a using-statement to the namespace of the code that are to be tested.

8) Add a using-statement for Moq:

using Moq;

9) Now to the exciting part - it's time to create your mock objects as well as the instance to your class to test:
     
        [TestMethod]
        public void EmailSender_SendEmail_Successfully()
        {
            //Arrange
            var emailProviderMock = new Mock<IEmailProvider>();
            var emailValidatorMock = new Mock<IEmailValidator>();

            emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true);
            emailProviderMock.Setup(m => m.SendEmail(It.IsAny<string>(), It.IsAny<string>(),
            It.IsAny<string>())).Returns(true);

            var emailSender = new EmailSender(emailProviderMock.Object, emailValidatorMock.Object);
           
            //Act
            //todo...

            //Assert
            //todo...
        }

It works like this:

* Create the mock objects from the Interfaces: new Mock<IEmailProvider>()

* Make sure to return the expected values if a method on the mock-object is called: emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true)

* Create an instance of our test subject with the instances of our mock-objects: new EmailSender(emailProviderMock.Object, emailValidatorMock.Object)

10) All we have to do now is the actual testing - i.e. call the method that are to be tested and assert that it returns what we're expecting:

            //Act
            var returnValue = emailSender.SendEmail("foo@example.com", "Welcome to our service", "Hi Foo,\n\nWelcome to service Bar!");

            //Assert
            Assert.IsTrue(returnValue);

As we're expecting everything to go as planned in this scenario, we're giving the method parameters that makes sense, we're returning sensible values from the mock-objects and we're asserting that the method returned a positive return value.

The test-method should now look like this as a whole:

        [TestMethod]
        public void EmailSender_SendEmail_Successfully()
        {
            //Arrange
            var emailProviderMock = new Mock<IEmailProvider>();
            var emailValidatorMock = new Mock<IEmailValidator>();

            emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true);
            emailProviderMock.Setup(m => m.SendEmail(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);

            var emailSender = new EmailSender(emailProviderMock.Object, emailValidatorMock.Object);

            //Act
            var returnValue = emailSender.SendEmail("foo@example.com", "Welcome to our service", "Hi Foo,\n\nWelcome to service Bar!");

            //Assert
            Assert.IsTrue(returnValue);
        }

Now go ahead and Run All tests in the Test Explorer and enjoy that green bar of success! :-)

Expecting problems

But what if everything doesn't go as planned. What if the caller didn't provide an email address? We'll we have to test for that too!

Looking at our EmailSender-class we can see that it is validating the incoming email address:

            if (string.IsNullOrEmpty(receiverEmailAddress))
                throw new ArgumentException("You must specify an email address",
                    nameof(receiverEmailAddress));

This means that the method will throw an ArgumentException if the receiverEmailAddress is empty. We can test for this by adding the ExpectedException-attribute to the test method. By doing this, the test will only succeed if the code throws an exception of the specified type. This is how it is works:
     
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void EmailSender_SendEmail_DidNotProvideEmailThrowsArgumentException()
        {
            //Arrange
            var emailProviderMock = new Mock<IEmailProvider>();
            var emailValidatorMock = new Mock<IEmailValidator>();

            emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true);
            emailProviderMock.Setup(m => m.SendEmail(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);

            var emailSender = new EmailSender(emailProviderMock.Object, emailValidatorMock.Object);

            //Act
            var returnValue = emailSender.SendEmail("", "Welcome to our service",
            "Hi Foo,\n\nWelcome to service Bar!");
        }

As you see, we're providing typeof(ArgumentException) to the ExpectedException-attribute and we're providing an empty string where the receiver email should be.

If you run this test is should also succeed!

Now you should know a bit of unit testing in C#, Microsoft Unit Test Framework and Moq. There are a lot more to learn but this should be enough to get you started writing your own unit tests. Make sure you do - it will make you a better coder.

Article created: Nov 19 '15. Edited Nov 19 '15.

1 Comment

3
Bojan Komazec [3]  •  Nov 29 '15  •   •  Reply

In general, it is a good introductory article but I would suggest one important thing:

Don't use ExpectedExceptionAttribute as it is not longer supported by NUnit (starting from 3.0 ). If you use ExpectedExceptionAttribute, test returns false positive if given exception is thrown from any place in the unit test, not only from a method under test.

I also have couple of other suggestions which are for a bit more advanced level but maybe still worth mentioning:

  • Test naming. Developer (and/or tester - where QA writes test cases) shall be able to get idea what are the test conditions and expected result by just reading test name.

EmailSender_SendEmail_Successfully() does not give much information about what successfully means (it doesn't throw exception? it returns boolean true?) and also what requirements arguments have to meet in order to make this method to succeed. I would suggest using fully descriptive test names, like:

 SendEmail_returns_true_when_receiverEmailAddress_is_valid_email_address_and_subject_and_body_are_non_empty_strings(). 

Following this logic, test names for negative cases could be:

SendEmail_throws_ArgumentException_when_receiverEmailAddress_is_NULL()
SendEmail_throws_ArgumentException_when_receiverEmailAddress_is_empty_string()
SendEmail_throws_ArgumentException_when_subject_is_NULL()
SendEmail_throws_ArgumentException_when_subject_is_empty_string()
etc...

Also, if test class is named EmailSenderTest (or better EmailSender_Test), meaning that EmailSender is SUT, there is no need to repeat its name in every test name. Test names can start with the name of the method tested.

  • There should be unit tests which verify that that dependencies are being called. In the case above, there shall be a unit test which verifies that SendEmail calls IEmailValidator.IsEmailValid:
SendEmail_calls_IEmailValidator_IsEmailValid()

...and also a test which verifies execution branch which throws exception:

SendEmail_throws_ArgumentException_when_IEmailValidator_IsEmailValid_returns_false()

Assuming there are unit tests covering IsEmailValid of the implementations of IEmailValidator, and with test stated above, there is no need to distinguish valid/non-valid email address in unit tests for EmailSender. Any string can be used and IEmailValidator.IsEmailValid can be mocked to always return true. We have to trust unit tests written for dependency classes and build new tests upon them. That is the reason why there is no need for test like:

SendEmail_throws_ArgumentException_when_receiverEmailAddress_is_not_valid_email_address()
  • Where test shall pass when input can be any value of a given type, that shall be explicitly stated in the test by using values made by the random value generator. In the case above, subject and body can be any non-null and non-empty strings so in the Arrange section you should assign them random string values (e.g. GUID).

Your comment

You need to sign up / log in to comment this article

Author

Created by Robert Bengtsson [126] Nov 19 '15

Share article

Do you know about

Processes?

Write an article