How to check for service unavailability and HTTP error

I'm new to Unit Testing and would like to simulate / test when a service is not available to ensure that the bug is fixed.

Scenario

REST API that queries Active Directory user accounts through LDAP / DirectorySearcher in C #. I see three possible results: User Found, User Not Found, and Service Not Available (DirectorySearcher). I have installed three tests for this, but each one always fails if I am connected to a domain or not. When connected, Test # 1, # 2 are successfully performed. If you disable test # 2, # 3 will succeed. Is my test overkill since the DirectoryServices library is already solid? My intention is to make sure that the web server throws an exception if it loses the ability to query Active Directory.

controller

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Web.Http;

namespace IdentitiesApi.Controllers
{
    public class UsersController : ApiController
    {
        // GET api/users/?username=admin
        public SearchResult Get([FromUri]string userName)
        {
            using (var searcher = new DirectorySearcher())
            {
                searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", userName);

                try
                {
                    SearchResult user = searcher.FindOne();

                    if (user == null)
                    {
                        var response = new HttpResponseMessage(HttpStatusCode.NotFound)
                        {
                            Content = new StringContent(string.Format("No user with username = \"{0}\" found.", userName)),
                            ReasonPhrase = "User Not Found"
                        };

                        throw new HttpResponseException(response);
                    }
                    else
                    {
                        return user;
                    }

                }
                catch (COMException)
                {
                    var response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
                    {
                        Content = new StringContent("The directory service could not be contacted. Please try again later."),
                        ReasonPhrase = "Directory Service Unavailable"
                    };

                    throw new HttpResponseException(response);
                }
            }
        }
    }
}

      

Unit tests

using System;
using System.DirectoryServices;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Web.Http;
using IdentitiesApi.Controllers;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace IdentitiesApi.Test
{
    [TestClass]
    public class UsersTest
    {
        [TestMethod]
        public void Single_AD_User()
        {
            // arrange
            var controller = new UsersController();
            SearchResult searchResult;

            string userName = "admin"; // existing user name
            string expected = "admin";
            string actual = "";

            // act
            searchResult = controller.Get(userName);

            // assert
            foreach (object value in searchResult.Properties["samAccountName"])
            {
                actual = value.ToString();
            }

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(HttpResponseException))]
        public void AD_User_Not_Found_Exception()
        {
            // arrange
            var controller = new UsersController();
            SearchResult searchResult;

            string userName = "?"; // invalid user name

            // act
            try
            {
                searchResult = controller.Get(userName);
            }
            catch (HttpResponseException ex)
            {
                // assert
                Assert.AreEqual(HttpStatusCode.NotFound, ex.Response.StatusCode);
                throw;
            }
        }

        [TestMethod]
        [ExpectedException(typeof(HttpResponseException))]
        public void AD_Service_Unavailable_Exception()
        {
            // arrange
            var controller = new UsersController();
            SearchResult searchResult;

            string userName = "admin";

            // act
            searchResult = controller.Get(userName);
        }
    }
}

      

+3


source to share


1 answer


The best way to test something like this is to use dependency injection for DirectorySearcher and then use mock in your unit test.

It looks like there is an IDirectorySearcher interface , although I don't know if DirectorySearcher implements it. Anyway, and it may be more than your request, here's what I recommend:

  • Keep your controllers lightweight . Right now, you have a ton of unused business logic in your actions. You are catching COM exceptions and your controllers are "aware" of AD low-level behavior. Instead, I'll write a wrapper to handle this and throw a generic exception. You avoid a lot of repetitive code (extra throw for both exceptions) and if you change how AD works, you can do it in one place.

  • Insert the wrapper into the controller. This will allow you to mock the service so that you can test all the different paths through your action.

When your controller is overwritten:

public class UsersController : ApiController
{
    private IDirectorySearcher _searcher;

    public UsersController(IDirectorySearcher searcher)
    {
        _searcher = searcher;
    }

    // GET api/users/?username=admin
    public SearchResult Get([FromUri]string userName)
    {
        try
        {
            return _searcher.FindSAMAccountName(userName);
        }

        catch (ADException ex)
        {
            var response = new HttpResponseMessage(HttpStatusCode.NotFound)
            {
                Content = ex.Content,
                ReasonPhrase = ex.Reason
            };

            throw new HttpResponseException(response);
        }
    }
}

      

And then your unit test (in this case, I'm using moq as my mocking library):



    [TestMethod]
    [ExpectedException(typeof(HttpResponseException))]
    public void AD_User_Not_Found_Exception()
    {
        var searcher = new Mock<IDirectorySearcher>();

        searcher.Setup(x => x.FindSAMAccountName(It.IsAny<string>()).Throws(new ADException());

        var controller = new UsersController(searcher.Object);

        try
        {
            SearchResult searchResult = controller.Get("doesn't matter. any argument throws");
        }
        catch (HttpResponseException ex)
        {
            // assert
            Assert.AreEqual(HttpStatusCode.NotFound, ex.Response.StatusCode);
            throw;
        }
    }

      

The beauty of using mock is that for each unit test, you can modify the call to Setup () to return whatever you want. It can return SearchResult or throw an exception or do nothing at all. You can even use

searcher.Verify(x => x.FindSAMAccountName(It.IsAny<string>()), Times.Once())

      

to check that the call has occurred exactly 1 time (or nothing, or something else).

This may be more than you asked for, but in general, the less complex each layer, the easier each unit test level.

+3


source







All Articles