DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Effective Engineering Feedback: Software Testing
  • The LLM Selection War Story: Part 2 - The Six LLM Failure Archetypes That Will Wreck Your Production System
  • Agentic Development: My Invisible Dev Team
  • Clean Code in the Age of Copilot: Why Semantics Matter More Than Ever

Trending

  • Migrate a Hardcoded LangGraph Agent to LaunchDarkly AI Configs in 20 Minutes
  • Stop Debugging Glue Jobs Manually: Building an Agentic Observability Layer for Data Pipelines
  • Observability for Agents and Workflows: Tracing Prompts, Tool Calls, and Business Outcomes End-to-End
  • When One MVP Is Really Four Systems: A Better Way to Plan Multi-Role Apps
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Unit Testing Asynchronous Web API Action Methods Using MS Test

Unit Testing Asynchronous Web API Action Methods Using MS Test

By 
Rabi Kiran Srirangam user avatar
Rabi Kiran Srirangam
·
Dec. 24, 13 · Interview
Likes (0)
Comment
Save
Tweet
Share
32.8K Views

Join the DZone community and get the full member experience.

Join For Free

Since Entity Framework now has a very nice support of performing all its actions asynchronously, the methods in the repositories in our projects will turn into asynchronous methods soon and so will be the code depending on it. Tom Fitzmacken did a nice job by putting together a tutorial on unit testing Web API 2 Controllers on official ASP.NET site. The tutorial discusses on testing synchronous action methods. The same techniques can be applied to test asynchronous action actions as well. In this post, we will see how easy it is to test asynchronous Web API action methods using MS Test. 

I created a simple repository interface with just one method in it. The implementation class uses Entity Framework to get a list of contacts from the database. 

public interface IRepository
{
    Task<IEnumerable<Contact>> GetAllContactsAsync();
}

public class Repository : IRepository
{
    ContactsContext context = new ContactsContext();

    public async Task<IEnumerable<Contact>> GetAllContactsAsync()
    {
        return await context.Contacts.ToArrayAsync();
    }
}

Following is the ASP.NET Web API controller that uses the above repository: 

public class ContactsController : ApiController
{
    IRepository repository;

    public ContactsController() : this(new Repository())
    { }

    public ContactsController(IRepository _repository)
    {
        repository = _repository;
    }

    [Route("api/contacts/plain")]
    public async Task<IEnumerable<Contact>> GetContactsListAsync()
    {
        IEnumerable<Contact> contacts;
         try
         {
            contacts = await repository.GetAllContactsAsync();
         }
         catch (Exception)
         {
             throw;
         }

         return contacts;
    }

    [Route("api/contacts/httpresult")]
    public async Task<IHttpActionResult> GetContactsHttpActionResultAsync()
    {
        IEnumerable<Contact> contacts;

        try
        {
            contacts = await repository.GetAllContactsAsync();
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }

        return Ok(contacts);
    }
}

As we see, the controller has two action methods performing the same task, but  the way they return the results is different. Since both of the action methods respond to HTTP GET method, I used attribute routing to distinguish them. I used poor man’s dependency injection to instantiate the repository; it can be easily replaced using an IoC container. 

Before writing unit tests for the above action methods, we need to create a mock repository. 

public class MockRepository:IRepository
{
    List<Contact> contacts;

    public bool FailGet { get; set; }

    public MockRepository()
    {
        contacts = new List<Contact>() {
            new Contact(){Id=1, Title="Title1", PhoneNumber="1992637281", CustomerId=1},
            new Contact(){Id=2, Title="Title2", PhoneNumber="9172735171", SupplierId=2},
            new Contact(){Id=3, Title="Title3", PhoneNumber="8361910353", CustomerId=2},
            new Contact(){Id=4, Title="Title4", PhoneNumber="7801274518", SupplierId=3}
        };
    }

    public async Task<IEnumerable<Contact>> GetAllContactsAsync()
    {
        if (FailGet)
        {
            throw new InvalidOperationException();
        }
        await Task.Delay(1000);
        return contacts;
    }
}

The property FailGet in the above class is used to force the mock to throw an exception. This is done just to cover more test cases. 

In the test class, we need a TestInitialize method to arrange the objects needed for unit testing. 

[TestClass]
public class ContactsControllerTests
{
    MockRepository repository;
    ContactsController contactsApi;

    [TestInitialize]
    public void InitializeForTests()
    {
        repository = new MockRepository();
        contactsApi = new ContactsController(repository);
    }
}

Let us test the GetContactsListAsync method first. Testing this method seems to be straight forward, as it returns either a plain generic list or throws an exception. But the test method can’t just return void like other tests, as the method is asynchronous. To test an asynchronous method, the test method should also be made asynchronous and return a Task. Following test checks if the controller action returns a collection of length 4: 

[TestMethod]
public async Task GetContacts_Should_Return_List_Of_Contacts() 
{
    var contacts = await contactsApi.GetContactsListAsync();
    Assert.AreEqual(contacts.Count(), 4);
}

If the repository encounters an exception, the exception is re-thrown from the GetContactsListAsync method as well. This case can be checked using the ExpectedException attribute. 

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task GetContacts_Should_Throw_Exception()
{
    repository.FailGet = true;
    var contacts = await contactsApi.GetContactsListAsync();
}

Now let’s test the GetContactsHttpActionResultAsync method. Though this method does the same thing as the previous method, it doesn’t return the plain .NET objects. To test this method, we need to extract the result from the IHttpActionResult object obtained from the action method. Following test checks if the action result contains a collection when the repository is able to fetch results. Return type of Ok() method used above is OkNegotiatedContentResult. IHttpActionresult has to be converted to this type to check for the result obtained: 

[TestMethod]
public async Task GetContactsHttpActionResult_Should_Return_HttpResult_With_Contacts()
{
    var contactsResult = await contactsApi.GetContactsHttpActionResultAsync() as OkNegotiatedContentResult<IEnumerable<Contact>>;

    Assert.AreEqual(contactsResult.Content.Count(), 4);
}

Similarly, in case of error, we are calling InternalServerError() method to return the exception for us. We need to convert the result to ExceptionResult type to be able to check the type of exception thrown. It is shown below: 

[TestMethod]
public async Task GetContactsHttpActionResult_Should_Return_HttpResult_With_Exception()
{
    repository.FailGet = true;
    var contactsResult = await contactsApi.GetContactsHttpActionResultAsync() as ExceptionResult;
    Assert.IsInstanceOfType(contactsResult.Exception,typeof(InvalidOperationException));
}

Happy coding!

unit test Web API

Published at DZone with permission of Rabi Kiran Srirangam. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Effective Engineering Feedback: Software Testing
  • The LLM Selection War Story: Part 2 - The Six LLM Failure Archetypes That Will Wreck Your Production System
  • Agentic Development: My Invisible Dev Team
  • Clean Code in the Age of Copilot: Why Semantics Matter More Than Ever

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook