RESTful Day Eight: Unit Testing And Integration Testing in WebAPI Using NUnit And Moq framework: Part Two

Table of Contents

  1. Introduction
  2. Roadmap
  3. Setup Solution
  4. Testing WebAPI
    • Step 1: Test Project
    • Step 2: Install the NUnit package
    • Step 3: Install the Moq framework
    • Step 4: Install Entity Framework
    • Step 5: Newtonsoft.Json
    • Step 6: References
  5. ProductController Tests
    • Tests Setup
    • Declare variables
    • Write Test Fixture Setup
    • Write Test Fixture Tear Down
    • Write Test Teardown
    • Mocking Repository
    • GetAllProductsTest ()
    • GetProductByIdTest ()
    • Testing Exceptions from WebAPI
    • GetProductByWrongIdTest ()
    • GetProductByInvalidIdTest ()
    • CreateProductTest ()
    • UpdateProductTest ()
    • DeleteProductTest ()
    • DeleteInvalidProductTest ()
    • DeleteProductWithWrongIdTest ()
    • Integration Tests
    • Difference between Unit tests and Integration tests
    • Conclusion

Introduction

In my last article, I explained how to write unit tests for the business service layer. In this article, we’ll learn how to write unit tests for WebAPI controllers i.e. REST’s actual endpoints. I’ll use the NUnit and Moq framework to write test cases for controller methods. I have already explained about installing NUnit and configuring unit tests. My last article also covered explaining about NUnit attributes used in writing unit tests. Please go through my last article of the series before following this article.

Roadmap

The following is the roadmap I have set up to learn WebAPI step by step.

Roadmap

I’ll purposely use Visual Studio 2010 and .net Framework 4.0 because there are a few implementations that are very hard to find in .Net Framework 4.0, but I’ll make it easy by showing how we can do it.

Setup Solution

When you take the code base from my last article and open it in Visual Studio, you’ll see the project structure something like as shown in the below image,

Setup Solution

The solution contains the WebAPI application and related projects. There are two newly added projects namedBusinessServices.Tests and TestHelper.We’ll use the TestHelper project and its classes for writing WebAPI unit tests in the same way we used it for writing business services unit tests.

Testing WebAPI

Step 1. Test Project

Add a simple class library in the existing visual studio and name it ApiController.Tests. Open Tools->Library Packet Manager->Packet Manager Console to open the package manager console window. We need to install the same packages as we did for business services before we proceed.

Library Packet Manager

Step 2. Install the NUnit package.

In the package manager console, select ApiController.Tests as default project and write command “Install-Package NUnit –Version 2.6.4”. When you run the command, it says NUnit is already installed, that’s because we installed this package for BusinessServices.Tests project, but doing this again for a new project (in our case it is ApiController.Tests) will not install it again but add a reference to the NUnit framework library and mark an entry in packages. config for ApiController. Tests project.

Install NUnit package

After it's successfully installed, you can see the dll reference in project references i.e. unit. framework.

Step 3. Install Moq framework

Install the framework on the same project in the similar way as explained in Step 2. Write the command “Install-Package Moq”.

Install-Package Moq

Step 4. Install Entity Framework

Install-Package EntityFramework -Version 5.0.0

Install Entity Framework

Step 5. Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for . NET. We’ll use it for serializing/de-serializing requests and responses.

Install-Package Newtonsoft.Json -Version 4.5.11

Newtonsoft.Json

Our packages.config i.e. automatically added in the project looks like.

<?xml version="1.0" encoding="utf-8"?>
<packages>
    <package id="EntityFramework" version="5.0.0" targetFramework="net40" />
    <package id="Moq" version="4.2.1510.2205" targetFramework="net40" />
    <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net40" />
    <package id="NUnit" version="2.6.4" targetFramework="net40" />
</packages>

Step 6. References

Add references of BusinessEntities, BusinessServices, DataModel, TestsHelper, and WebApi project to this project.

References

ProductControllerTests

We’ll start with setting up the project and setting up the prerequisites for tests and gradually move on to actual tests.

Tests Setup

Add a new class named ProductControllerTest.csin ApiController.Tests project.

Declare variables

Define the private variables that we’ll use in the class to write tests.

#region Variables   
private IProductServices _productService;   
private ITokenServices _tokenService;   
private IUnitOfWork _unitOfWork;   
private List<Product> _products;   
private List<Token> _tokens;   
private GenericRepository<Product> _productRepository;   
private GenericRepository<Token> _tokenRepository;   
private WebApiDbEntities _dbEntities;   
private HttpClient _client;   
private HttpResponseMessage _response;   
private string _token;   
private const string ServiceBaseURL = "http://localhost:50875/";   
#endregion

Variable declarations are self-explanatory where _productService will hold mock for ProductServices, _tokenService will hold mock for TokenServices, _unitOfWork for UnitOfWork class, _products will hold dummy products from DataInitializer class of TestHelper project, __tokens will hold dummy tokens from DataInitializer class of TestHelper project,_productRepository, tokenRepositoryand _dbEntities holds mock for Product Repository, Token Repository and WebAPIDbEntities from DataModel project respectively.

Since WebAPI is supposed to return a response in HttpResponse format _response is declared to store the returned response against which we can assert._token holds the token value after successful authentication._client and ServiceBaseURL may not be required in this article’s context, but you can use them to write integration tests that purposely use actual API URLs and test on the actual database.

Write Test Fixture Setup

Write test fixture setup method with [TestFixtureSetUp] attribute at the top, this method runs only one time when tests are executed.

[TestFixtureSetUp]
public void Setup()
{
    _products = SetUpProducts();
    _tokens = SetUpTokens();
    _dbEntities = new Mock<WebApiDbEntities>().Object;
    _tokenRepository = SetUpTokenRepository();
    _productRepository = SetUpProductRepository();
    var unitOfWork = new Mock<IUnitOfWork>();
    unitOfWork.SetupGet(s => s.ProductRepository).Returns(_productRepository);
    unitOfWork.SetupGet(s => s.TokenRepository).Returns(_tokenRepository);
    _unitOfWork = unitOfWork.Object;
    _productService = new ProductServices(_unitOfWork);
    _tokenService = new TokenServices(_unitOfWork);
    _client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
    var tokenEntity = _tokenService.GenerateToken(1);
    _token = tokenEntity.AuthToken;
    _client.DefaultRequestHeaders.Add("Token", _token);
}

The purpose of this method is similar to that of the method we wrote for business services.hereSetupProducts() will fetch the list of dummy products and SetupTokens() will get the list of dummy tokens. We will try to set up a mock for the Token and Product repository as well, and after that mock UnitOfWork and set it up against already mocked token and product repository._prdouctService and _tokenService are the instances of ProductService and TokenService respectively, both initialized with mocked Unit of Work.

Following is the line of code that I would like to explain more.

var tokenEntity = _tokenService.GenerateToken(1);
_token = tokenEntity.AuthToken;
_client.DefaultRequestHeaders.Add("Token", _token);

In the above code, we are initializing the _client i.e. HttpClient with Token value in the request header. We are doing this because, if you remember about the security (Authentication and Authorization) we implemented in Product Controller, which says no request will be entertained unless it is authorized i.e. contains an authentication token in its header. So here we generate the token via TokenService’s GenerateToken() method, passing a default used id as “1”, and use that token for authorization. We require this only to perform integration testing as for unit tests we would directly be calling controller methods from our unit tests methods, but for actual integration tests, you’ll have to mock all pre-conditions before calling an API endpoint.

SetUpProducts()

private static List<Product> SetUpProducts()
{
    var prodId = new int();
    var products = DataInitializer.GetAllProducts();
    foreach (Product prod in products)
        prod.ProductId = ++prodId;
    return products;
}

SetUpTokens()

private static List<Token> SetUpTokens()
{
    var tokId = new int();
    var tokens = DataInitializer.GetAllTokens();
    foreach (Token tok in tokens)
        tok.TokenId = ++tokId;
    return tokens;
}

Write Test Fixture Tear Down

Unlike [TestFixtureTearDow n] ear down is used to de-allocate or dispose of the objects.

Following is the code for teardown.

[TestFixtureTearDown]
public void DisposeAllObjects()
{
    _tokenService = null;
    _productService = null;
    _unitOfWork = null;
    _tokenRepository = null;
    _productRepository = null;
    _tokens = null;
    _products = null;
    if (_response != null)
        _response.Dispose();
    if (_client != null)
        _client.Dispose();
}

Write Test Setup

In this case, Setup is only required if you write an integration test. So you can choose to omit this.

[SetUp]
public void ReInitializeTest()
{
    _client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
    _client.DefaultRequestHeaders.Add("Token", _token);
}

Write Test Teardown

Test [TearDown] is invoked after every test execution is complete.

[TearDown]
public void DisposeTest()
{
    if (_response != null)
        _response.Dispose();
    if (_client != null)
        _client.Dispose();
}

Mocking Repository

I have created a method SetUpProductRepository() to mock Product Repository and assign it to _productrepository in ReInitializeTest() method and SetUpTokenRepository() to mock TokenRepository and assign that to _tokenRepository in ReInitializeTest() method.

SetUpProductRepository()

private GenericRepository<Product> SetUpProductRepository()
{
    // Initialise repository
    var mockRepo = new Mock<GenericRepository<Product>>(MockBehavior.Default, _dbEntities);
    
    // Setup mocking behavior
    mockRepo.Setup(p => p.GetAll()).Returns(_products);
    
    mockRepo.Setup(p => p.GetByID(It.IsAny<int>()))
            .Returns(new Func<int, Product>(
                id => _products.Find(p => p.ProductId.Equals(id))));
    
    mockRepo.Setup(p => p.Insert((It.IsAny<Product>())))
            .Callback(new Action<Product>(newProduct =>
            {
                dynamic maxProductID = _products.Last().ProductId;
                dynamic nextProductID = maxProductID + 1;
                newProduct.ProductId = nextProductID;
                _products.Add(newProduct);
            }));
    
    mockRepo.Setup(p => p.Update(It.IsAny<Product>()))
            .Callback(new Action<Product>(prod =>
            {
                var oldProduct = _products.Find(a => a.ProductId == prod.ProductId);
                oldProduct = prod;
            }));
    
    mockRepo.Setup(p => p.Delete(It.IsAny<Product>()))
            .Callback(new Action<Product>(prod =>
            {
                var productToRemove = 
                    _products.Find(a => a.ProductId == prod.ProductId);
                
                if (productToRemove != null)
                    _products.Remove(productToRemove);
            }));
    
    // Return mock implementation object
    return mockRepo.Object;
}

SetUpTokenRepository()

private GenericRepository<Token> SetUpTokenRepository()   
{   
    // Initialise repository   
    var mockRepo = new Mock<GenericRepository<Token>>(MockBehavior.Default, _dbEntities);   
  
    // Setup mocking behavior   
    mockRepo.Setup(p => p.GetAll()).Returns(_tokens);   
  
    mockRepo.Setup(p => p.GetByID(It.IsAny<int>()))   
            .Returns(new Func<int, Token>(   
            id => _tokens.Find(p => p.TokenId.Equals(id))));   
  
    mockRepo.Setup(p => p.Insert((It.IsAny<Token>())))   
            .Callback(new Action<Token>(newToken =>   
            {   
                dynamic maxTokenID = _tokens.Last().TokenId;   
                dynamic nextTokenID = maxTokenID + 1;   
                newToken.TokenId = nextTokenID;   
                _tokens.Add(newToken);   
            }));   
  
    mockRepo.Setup(p => p.Update(It.IsAny<Token>()))   
            .Callback(new Action<Token>(token =>   
            {   
                var oldToken = _tokens.Find(a => a.TokenId == token.TokenId);   
                oldToken = token;   
            }));   
  
    mockRepo.Setup(p => p.Delete(It.IsAny<Token>()))   
            .Callback(new Action<Token>(prod =>   
            {   
                var tokenToRemove =   
                _tokens.Find(a => a.TokenId == prod.TokenId);   
  
                if (tokenToRemove != null)   
                    _tokens.Remove(tokenToRemove);   
            }));   
  
    // Return mock implementation object   
    return mockRepo.Object;   
}   

Unit Tests

Unit Tests

All is set now, and we are ready to write unit tests for ProductController. We’ll write a test to perform all the CRUD operations and all the action exit points that are part of ProductController.

GetAllProductsTest ()

Our ProductService in BusinessServices project contains a method named GetAllProducts (), following is the implementation.

[Test]
public void GetAllProductsTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/all")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    _response = productController.Get();

    var responseResult = JsonConvert.DeserializeObject<List<Product>>(_response.Content.ReadAsStringAsync().Result);
    Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
    Assert.AreEqual(responseResult.Any(), true);
    var comparer = new ProductComparer();
    CollectionAssert.AreEqual(
        responseResult.OrderBy(product => product, comparer),
        _products.OrderBy(product => product, comparer), comparer);
}

Let me explain the code step by step. We start the code by creating an instance of ProductController and initialize the Request property of the controller with a new request message stating calling HTTP method as GET and initialize the RequestUri with base hosted service URL and append with the actual endpoint of the method. Initializing RequestUri is not necessary in this case but will help you if you test the actual service endpoint. In this case, we are not testing the actual endpoint but the direct controller method.

HttpPropertyKeys.HttpConfigurationKey, newHttpConfiguration() line adds default HTTP configuration to HttpConfigurationKey necessary for controller instance instantiation.

_response = productController.Get(); line calls the Get() method of the controller that fetches all the products from the dummy _products list. Since the return type of the method was an HTTP response message, we need to parse it to get the JSON result sent from the method. All the transactions from APIs should ideally happen in the form of JSON or XML only. This helps the client to understand the response and its result set. We de-serialize the object we got from _response using the NewtonSoft library into a list of products. That means the JSON response is converted to List<Product> for better accessibility and comparison. Once done with JSON to List object conversion, I have put three asserts to test the result.

Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK); line checks the HTTP status code of the response, the expected is HttpStatusCode.OK.

Second assert i.e. Assert.AreEqual(responseResult.Any(), true); checks that we have got the items in the list or not. The third assert is the actual confirmation assert for our test that compares each product from the actual product list to the returned product list.

ResponseResult

We got both the list, and we need to check the comparison of the lists, I just pressed F5 and got the result on TestUI as.

Result on TestUI

This shows our test is passed, i.e. the expected and returned result is the same.

GetProductByIdTest()

This unit test verifies if the correct result is returned if we try to invoke the GetProductById() method of the product controller.

[Test]
public void GetProductByIdTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/productid/2")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
    _response = productController.Get(2);
    
    var responseResult = JsonConvert.DeserializeObject<Product>(_response.Content.ReadAsStringAsync().Result);
    Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
    AssertObjects.PropertyValuesAreEquals(responseResult, _products.Find(a => a.ProductName.Contains("Mobile")));
}

I have used a sample product ID “2” to test the method. Again we get the result in JSON format inside HttpResponse that we de-serialize. The first assert compares the status code and the second assert makes use of the AssertObject class to compare the properties of the returned product with the actual “mobile” named product having product ID as 2 from the list of products.

HttpResponse

Compile

Testing Exceptions from WebAPI

NUnit provides flexibility to even test the exceptions. Now if we want to unit test the alternate exit point for the GetProductById() method i.e. an exception what should we do? Remember it was easy to test the alternate exit point for the business services method because it returned null. Now in the case of exception, NUnit provides an attribute ExpectedException. We can define the type of exception expected to be returned from the method call. Like if I make a call to the same method with the wrong id, the expectation is that it should return an exception with ErrorCode 1001 and an error description saying "No product found for this ID.".

So in our case, the expected exception type is ApiDataException(got it from the controller method). Therefore we can define the Exception attribute as [ExpectedException("WebApi.ErrorHelper.ApiDataException")] and call the controller method with the wrong id. But there is an alternate way to assert the exception. NUnit also provides us the flexibility to assert the exception by Assert. Throws. This statement asserts the exception and returns that particular exception to the caller. Once we get that particular exception we can assert it with its ErrorCode and ErrorDescription or on whatever property you want to.

GetProductByWrongIdTest()

[Test]
//[ExpectedException("WebApi.ErrorHelper.ApiDataException")]
public void GetProductByWrongIdTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/productid/10")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    var ex = Assert.Throws<ApiDataException>(() => productController.Get(10));
    Assert.That(ex.ErrorCode, Is.EqualTo(1001));
    Assert.That(ex.ErrorDescription, Is.EqualTo("No product found for this id."));
}

In the above code, I have commented out the Exception attribute approach and followed the alternate one.

I called the method with the wrong id (that does not exist in our product list) in the statement.

var ex = Assert.Throws<ApiDataException>(() => productController.Get(10));

The above statement expects ApiDataException and stores the returned exception in “ex”.

Now we can assert the “ex” exception properties like ErrorCode and ErrorDescription with the actual desired result.

ErrorCode

GetProductByInvalidIdTest

GetProductByInvalidIdTest ()

Another exit point for the same method is that if the request for a product comes with an invalid it then an exception is thrown. Let’s test that method for this scenario.

[Test]
// [ExpectedException("WebApi.ErrorHelper.ApiException")]
public void GetProductByInvalidIdTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/productid/-1")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    var ex = Assert.Throws<ApiException>(() => productController.Get(-1));
    Assert.That(ex.ErrorCode, Is.EqualTo((int)HttpStatusCode.BadRequest));
    Assert.That(ex.ErrorDescription, Is.EqualTo("Bad Request..."));
}

I passed an invalid id i.e. -1 to the controller method and it throws an exception of type ApiException with ErrorCode equal to HttpStatusCode.BadRequest and ErrorDescription equal to “bad Request.

Bad Request

Test result.

Test result

i.e. Passed. Other tests are very much of the same kind as I explained.

CreateProductTest ()

/// <summary>
/// Create product test
/// </summary>
[Test]
public void CreateProductTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/Create")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    var newProduct = new ProductEntity()
    {
        ProductName = "Android Phone"
    };

    var maxProductIDBeforeAdd = _products.Max(a => a.ProductId);
    newProduct.ProductId = maxProductIDBeforeAdd + 1;
    productController.Post(newProduct);
    var addedProduct = new Product() { ProductName = newProduct.ProductName, ProductId = newProduct.ProductId };
    AssertObjects.PropertyValuesAreEquals(addedProduct, _products.Last());
    Assert.That(maxProductIDBeforeAdd + 1, Is.EqualTo(_products.Last().ProductId));
}

UpdateProductTest ()

/// <summary>  
/// Update product test  
/// </summary>  
[Test]  
public void UpdateProductTest()  
{  
    var productController = new ProductController(_productService)  
    {  
        Request = new HttpRequestMessage  
        {  
            Method = HttpMethod.Put,  
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/Modify")  
        }  
    };  
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());  
  
    var firstProduct = _products.First();  
    firstProduct.ProductName = "Laptop updated";  
    var updatedProduct = new ProductEntity() { ProductName = firstProduct.ProductName, ProductId = firstProduct.ProductId };  
    productController.Put(firstProduct.ProductId, updatedProduct);  
    Assert.That(firstProduct.ProductId, Is.EqualTo(1)); // hasn't changed  
}

DeleteProductTest ()

/// <summary>
/// Delete product test
/// </summary>
[Test]
public void DeleteProductTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Put,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/Remove")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    int maxID = _products.Max(a => a.ProductId); // Before removal
    var lastProduct = _products.Last();

    // Remove last Product
    productController.Delete(lastProduct.ProductId);
    Assert.That(maxID, Is.GreaterThan(_products.Max(a => a.ProductId))); // Max id reduced by 1
}

DeleteInvalidProductTest ()

/// <summary>
/// Delete product test with invalid id
/// </summary>
[Test]
public void DeleteProductInvalidIdTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Put,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/remove")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    var ex = Assert.Throws<ApiException>(() => productController.Delete(-1));
    Assert.That(ex.ErrorCode, Is.EqualTo((int)HttpStatusCode.BadRequest));
    Assert.That(ex.ErrorDescription, Is.EqualTo("Bad Request..."));
}

DeleteProductWithWrongIdTest ()

/// <summary>
/// Delete product test with wrong id
/// </summary>
[Test]
public void DeleteProductWrongIdTest()
{
    var productController = new ProductController(_productService)
    {
        Request = new HttpRequestMessage
        {
            Method = HttpMethod.Put,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/remove")
        }
    };
    productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    int maxID = _products.Max(a => a.ProductId); // Before removal

    var ex = Assert.Throws<ApiDataException>(() => productController.Delete(maxID + 1));
    Assert.That(ex.ErrorCode, Is.EqualTo(1002));
    Assert.That(ex.ErrorDescription, Is.EqualTo("Product is already deleted or not exist in system."));
}

All the above-mentioned tests are self-explanatory and are more like how we tested business services. The idea was to explain how we write tests in WebAPI.Let’s run all the tests through NUnit UI.

Test through NUnit UI

NUnit UI

Step 1. Launch NUnit UI. I have already explained how to install NUnit on the Windows machine. Just launch the NUnit interface with its launch icon.

Launch NUnit UI

Step 2. Once the interface opens, click on File -> New Project and name the project as WebAPI.nunit, and save it at any windows location.

New Project

File

WebAPI.nunit

Step 3. Now, click on Project-> Add Assembly and browse for ApiController.Tests.dll (The library created for your unit test project when compiled)

Library

ApiController.Tests.dll

Step 4. Once the assembly is browsed, you’ll see all the unit tests for that test project gets loaded in the UI and are visible on the interface.

Tests of Api controller

At the right-hand side panel of the interface, you’ll see a Run button that runs all the tests of the API controller. Just select the node ApiController in the tests tree on the left side and press the Run button on the right side.

ApiController

Once you run the tests, you’ll get a green progress bar on the right side and a tick mark on all the tests on the left side. That means all the tests are passed. In case any test fails, you’ll get a cross mark on the test and the red progress bar on the right side.

Run

But here, all of our tests are passed.

Tests

Integration Tests

I’ll give just an idea of what integration tests are and how can we write them. The integration test doesn’t run in memory. For WebAPI the best practice to write an integration test is when the WebAPI is self-hosted. You can try writing an integration test when you host an API so that you you get an actual URL or endpoint of the service you want to test. The test is performed on actual data and actual services. Let’s proceed with an example. I have hosted my web API and I want to test the GetAllProducts() method of WebAPI.My hosted URL for the particular controller action is http://localhost:50875/v1/Products/Product/allproducts.

Now I know that I am not going to test my controller method through dll reference but I want to actually test its endpoint for which I need to pass an authentication token because that endpoint is secured and can not be authorized until I add a secure token to the Request header. Following is the integration test for GetAllProducts().

[Test]
public void GetAllProductsIntegrationTest()
{
    #region To be written inside Setup method specifically for integration tests
    var client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
    client.DefaultRequestHeaders.Add("Authorization", "Basic YWtoaWw6YWtoaWw=");
    MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
    _response = client.PostAsync("login", null).Result;

    if (_response != null && _response.Headers != null && _response.Headers.Contains("Token") && _response.Headers.GetValues("Token") != null)
    {
        client.DefaultRequestHeaders.Clear();
        _token = ((string[])(_response.Headers.GetValues("Token")))[0];
        client.DefaultRequestHeaders.Add("Token", _token);
    }
    #endregion

    _response = client.GetAsync("v1/Products/Product/allproducts/").Result;
    var responseResult = JsonConvert.DeserializeObject<List<ProductEntity>>(_response.Content.ReadAsStringAsync().Result);
    Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
    Assert.AreEqual(responseResult.Any(), true);
}

I have used the same class to write this test, but you should always keep your unit tests segregated from integration tests, so use another test project to write integration tests for web API. First, we have to request a token and add it to client's request.

var client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
client.DefaultRequestHeaders.Add("Authorization", "Basic YWtoaWw6YWtoaWw=");
MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
_response = client.PostAsync("login", null).Result;

if (_response != null && _response.Headers != null && _response.Headers.Contains("Token") && _response.Headers.GetValues("Token") != null)
{
    client.DefaultRequestHeaders.Clear();
    _token = ((string[])(_response.Headers.GetValues("Token")))[0];
    client.DefaultRequestHeaders.Add("Token", _token);
}

In the above code, I have initialized the client with the running service’s base URL i.e. http://localhost:50875. After initialization, I am setting a default request header to call my login endpoint of the Authentication controller to fetch a valid token. Once the user logs in with his credentials he gets a valid token. To read in detail about security refer to my article on security in web API. I have passed the base 64 string of my credentials username: Akhil and password: Akhil for basic authentication. Once the request gets authenticated, I get a valid token in _response. Headers that I fetch and assign to _token variable and add to the client’s default header with this line of code.

_token = ((string[])(_response.Headers.GetValues("Token")))[0];
client.DefaultRequestHeaders.Add("Token", _token);

Then I am calling the actual service URL from the same client.

_response = client.GetAsync("v1/Products/Product/allproducts/").Result;

And we get the result as a success. Follow the screenshots.

Step 1. Get Token

Get Token

We got the token: 4bffc06f-d8b1-4eda-b4e6-df9568dd53b1. Now since this is a real-time test. This token should be saved in the database. Let’s check.

Step 2. Check database

Check database

We got the same token in the database. It proves we are testing on real live URLs.

Step 3. Check Response Result.

Response Result

Here we got the response to result with 6 products where the first product ID is 1 and the product name is “Laptop”. Check the database for a complete product list.

Result

We get the same data. This proves our test is a success.

Data

Likewise, you can write more integration tests.

Difference between Unit tests and Integration tests

I’ll not write much, but wanted to share one of my good readings on this from this reference link.

Unit Testing Integration Testing
Unit testing is a type of testing to check if the small piece of code is doing what it is supposed to do. Integration testing is a type of testing to check if different pieces of the modules are working together.
Unit testing checks a single component of an application. The behavior of integration modules is considered in the Integration testing.
The scope of Unit testing is narrow, it covers the Unit or small piece of code under test. Therefore while writing a unit test shorter codes are used that target just a single class. The scope of Integration testing is wide, it covers the whole application under test and it requires much more effort to put together.
Unit tests should have no dependencies on code outside the unit tested. Integration testing is dependent on other outside systems like databases, hardware allocated for them, etc.
This is the first type of testing to be carried out in the Software testing life cycle and generally executed by the developer. This type of testing is carried out after Unit testing and before System testing and executed by the testing team.
Unit testing is not further subdivided into different types. Integration testing is further divided into different types as follows:
  Top-down Integration, Bottom-Up Integration, and so on.
Unit testing starts with the module specification. Integration testing starts with the interface specification.
The detailed visibility of the code comes under Unit testing. The visibility of the integration structure comes under Integration testing.
Unit testing mainly focus on testing the functionality of individual units only and does not uncover the issues arises when different modules are interacting with each other. Integration testing is to be carried out to discover the issues that arise when different modules are interacting with each other to build the overall system.
The goal of Unit testing is to test each unit separately and ensure that each unit is working as expected. The goal of Integration testing is to test the combined modules together and ensure that every combined module is working as expected.
Unit testing comes under the White box testing type. Integration testing is comes under both the Black box and White box types of testing.

Table reference.

Conclusion

Conclusion

In this article, we learned how to write unit tests for Web API controllers and primarily on basic CRUD operations. The purpose was to get a basic idea of how unit tests are written and executed. You can add your own flavor to this that helps you in your real-time project. We also learned about how to write integration tests for WebAPI endpoints. I hope this was useful to you. You can download the complete source code of this article with packages from GitHub.

Read more

For more technical articles you can reach out to CodeTeddy

My other series of articles.

Read more articles on ASP.NET.


Similar Articles