SHARE:

Understanding the Difference Between Unit Test and Integration Test

This article is not intended to exhaustively describe unit tests, but rather to promote a clear understanding of their purpose, and serve as a warning to developers who often confuse unit testing and integration testing (that includes myself, when I was starting out).

What is a Unit Test ?

unit test must be truly unitary. To achieve this :

  • The class must be tested in complete isolation from the SUT (System Under Test)
  •  to ensure that only the class and its methods are being tested.

  • Isolate the dependencies of the tested class by using the “Dependency injection”
  •   pattern.

  • Use a mocking framework to inject dependencies in the form of caps.

Since unit tests should run as fast as possible in order to provide an almost immediate return, they must be banned from accessing files, databases or external services. Tests that talk to a database are not unit tests; they are integration tests.

Unit Test Example

As an example of a unit test where the class is isolated from the SUT, we will use the “Rhino.Mocks” mocking framework. A good article on its uses can be found here.

First, here is the controller to be tested :

using System.Web.Mvc;

namespace MyApp.Controllers
{
    public class EmailController : Controller
    {
        private IEmailService _emailService;

        public EmailController(IEmailService emailService)
        {
            _emailService = emailService;
        }

        // Allows to verify if an email already exists
        [HttpPost]
        public ActionResult VerifyEmail(string email)
        {
            if (_emailService.VerifyEmail(email))
                return RedirectToAction("ProceedRegistration");

            return Content("Email already used!");
        }

       public ActionResult ProceedRegistration()
        {
            return View();
        }
    }
}

Second, here is the sample unit test:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using MyApp.Services;
using MyApp.Controllers;
using System.Web.Mvc;


namespace TestMyApp
{
    [TestClass]
    public class TestMyApp_Isolation
    {
        //private IEmailService _mockEmailService;
        
        [TestMethod]
        public void TestRecognized()
        {
            IEmailService mockEmailService = MockRepository.GenerateMock();

            // Arrange
            mockEmailService.Expect(m => m.VerifyEmail(Arg.Is.Anything)).Return(true);
            EmailController emailController = new EmailController(mockEmailService);

            // Act
            var result = emailController.VerifyEmail(Arg.Is.Anything);

            // Assert
            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
            Assert.AreEqual("ProceedRegistration", ((RedirectToRouteResult)result).RouteValues["action"]);
        }

        [TestMethod]
        public void TestNotRecognized()
        {
            IEmailService mockEmailService = MockRepository.GenerateMock();

            // Arrange
            mockEmailService.Expect(m => m.VerifyEmail(Arg.Is.Anything)).Return(false);
            EmailController emailController = new EmailController(mockEmailService);

            // Act
            var result = emailController.VerifyEmail(Arg.Is.Anything);

            // Assert
            Assert.IsInstanceOfType(result, typeof(ContentResult));
            Assert.AreEqual("Email already used!", ((ContentResult)result).Content);
        }
    }
}

In this example I am testing the behaviour of a controller responsible for verifying the existence of an email. I want to know how it behaves when it tests an email that is already used and an email that is new.

The following are the well-known unit test stages:

  •  “Arrange” to prepare the behavior to be tested;
  • “Act” execute the items to be test;
  • “Assert” to validate the behavior of the test unit.

You will notice that I created the fake object “IEmailService mockEmailService MockRepository.GenerateMock ();” whose behaviour I “arrange” (expecting a specific behaviour from the EmailService) “mockEmailService.Expect (m => m.VerifyEmail (Arg .Is.Anything)) Return (true);”

I am now able to test the behavior of my action method in the “VerifyEmail” controller by getting the result obtained after performing the action “Act” : “var result = emailController.VerifyEmail (Arg .Is.Anything);”

Faced with an unrecognized email, the controller should redirect to another action to continue enrollment in the website. I am actually testing the controller to check whether it is actually a redirect, and whether it redirects to the right Action controller to continue the work with “Assert” :

“Assert.IsInstanceOfType (result, typeof (RedirectToRouteResult));” and “Assert.AreEqual (” ProceedRegistration “((RedirectToRouteResult) result) .RouteValues ​​[” action “]);”

Now, let’s test behavior when an email is recognized:

  1. “Arrange” : “mockEmailService.Expect(m => m.VerifyEmail(Arg.Is.Anything)).Return(false);”
  2. “Act” : “var result = emailController.VerifyEmail(Arg.Is.Anything);”
  3. “Assert” : “Assert.IsInstanceOfType(result, typeof(ContentResult));” and “Assert.AreEqual(“Email already used!”, ((ContentResult)result).Content);”

This time the expected return type is “ContentResult” with a specific error message “Email already used!”

This example has shown how to test the behavior of a controller without any dependencies, we performed a real, isolated, unit test!

Example of an integration test

Now let’s look at an integration test using the same example as previously:

Let’s assume that the concrete class “EmailService” accesses DataBase.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using MyApp.Services;
using MyApp.Controllers;
using System.Web.Mvc;

namespace TestMyApp
{
    [TestClass]
    public class TestMyApp_Integration
    {
        [TestMethod]
        public void TestRecognized()
        {
            // Arrange
            string email = "recognizedEmail@test.com";
            EmailController emailController = new EmailController(new EmailService());

            // Act
            var result = emailController.VerifyEmail(email);

            // Assert
            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
            Assert.AreEqual("ProceedRegistration", ((RedirectToRouteResult)result).RouteValues["action"]);
        }

        [TestMethod]
        public void TestNotRecognized()
        {
            // Arrange
            string email = "NotRecognizedEmail@test.com";
            EmailController emailController = new EmailController(new EmailService());

            // Act
            var result = emailController.VerifyEmail(email);

            // Assert
            Assert.IsInstanceOfType(result, typeof(ContentResult));
            Assert.AreEqual("Email already used!", ((ContentResult)result).Content);
        }
    }
}

This example highlights the difference between this test and the unit test: I don’t set up an artificial behavior in the “Arrange” section, I have a real “email” parameter in a real class method with a call to the database!, and I check controller behavior with external dependencies during an actual use of the functionality.

I hope this example has helped you understand the difference between a unit test and an integration test. Feel free to leave any questions in the “comment” section 🙂

Now, you should be able to tell the difference between a real unit test and an integration test.

Written by

anthonygiretti

Anthony is a specialist in Web technologies (14 years of experience), in particular Microsoft .NET and learns the Cloud Azure platform. He has received twice the Microsoft MVP award and he is also certified Microsoft MCSD and Azure Fundamentals.