Home > Mobile >  Writing Unit Test cases for Web Api in .Net
Writing Unit Test cases for Web Api in .Net

Time:09-03

I wanted to write unit test cases for my .Net project . This is How my Controller looks like

using backend.Models;
using backend.services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace backend.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class GuestTypeController : ControllerBase
    {
        private readonly GuestTypeService _guestType;
        public GuestTypeController(GuestTypeService _guestType)
        {
            this._guestType = _guestType;

        }
        [HttpGet("GuestType")]

        
         public List<GuestType> guestType()
        {

            return _guestType.getGuestType();
        }
    }
}


This is how Service class looks like


namespace backend.services
{
    public class GuestTypeService
    {
        private readonly bookingengineContext dbContext;
        public GuestTypeService(bookingengineContext dbContext)
        {
            this.dbContext = dbContext;
        }

        public List<GuestType> getGuestType()
        {
              
            return dbContext.GuestTypes.ToList();
        }
    }
}

I have object of service in my constructor of controller . While writing test cases I am calling my controller and mocking my db context and services class. But since my controller contains service class object in contstructor i am not able to test it . What is the way to test my controlller. This is my first time writing unit testing . So I am struggling.

CodePudding user response:

Testing is actually easier this way. You can create the object instances you want. You don't even have to mock the DbContext. You can create one targeting either the in-memory provider or SQLite in memory mode. The EF Core docs have a section that explains the available testing options, including the various options for test doubles.

Repository, or at least interfaces

The Repository option doesn't mean creating an interface named IRepository with full CRUD methods. It means abstracting the actual data access, allowing it to change without modifying the rest of the program. In this case, your service is doing that. If the service implemented an interface you could mock it or create dummy test services:

public interface IGuestTypeService
{
    List<GuestType> GetGuestTypes();
}

public GuestTypeService:IGuestTypeService
{
    ...
    public List<GuestType> GetGuestTypes()
    {
        ....
    }
}

The service should be registered through its interface:

serviced.AddScoped<IGuestTypeService,GuestTypeService>();

And the container should accept the interface

public class GuestTypeController : ControllerBase
{
    private readonly IGuestTypeService _guestType;
    public GuestTypeController(IGuestTypeService _guestType)
    {
        this._guestType = _guestType;
    }

In your test, you can create a dummy test service:

class DummyService:IGuestService
{
    public List<GuestType> GuestTypes{get;set;}

    public List<GuestType> GetGuestType()=>GuestTypes;
}


[Fact]
public void MyTest()
{
    var guestTypes=new List<GuestType>{ ....};
    var service=new DummyService {GuestTypes = guestTypes};

    var controller=new GuestTypeController(service);

    var actual=controller.getGuestType();

    Assert.Equal(actual,guestTypes);
}

Test DbContext

It's also possible to create a DbContext backed by SQLite in in-memory mode or a memory collection. SQLite provides full SQL support while the in-memory provider is essentially a wrapped list and breaks down in complex operations.

Testing with SQLite is explained in the SQLite in-memory section.

In this case, the test creates and loads the DbContext and injects it into the actual service:

public class MyTestClass:IDisposable
{
    SQLiteConnection _connection;
    DbContextOptions<bookingengineContext> _contextOptions;

    public MyTestClass()
    {
        // Create and open a connection. This creates the SQLite in-memory database, which will persist until the connection is closed
        // at the end of the test (see Dispose below).
        _connection = new SqliteConnection("Filename=:memory:");
        _connection.Open();

        // These options will be used by the context instances in this test suite, including the connection opened above.
        _contextOptions = new DbContextOptionsBuilder<bookingengineContext>()
            .UseSqlite(_connection)
            .Options;

        SeedData();
    }


 
    public void Dispose() => _connection.Dispose();

    bookingengineContext CreateContext() => new bookingengineContext(_contextOptions);

Test data is inserted into the database through SeedData()

void SeedData()
{
    var data=new[]
    {
        new GuestType { ... },
        new GuestType { ... }
    };

    using var context = new bookingengineContext(_contextOptions);
    context.Database.EnsureCreated();
    context.AddRange(data);
    context.SaveChanges();
}

The test can create a new DbContext on top of the in-memory database as needed. The actual test isn't all that different from the previous one

[Fact]
public void MyTest()
{
    using var db=CreateContext();
    var service=new GuestTypeService(db);

    var controller=new GuestTypeController(service);

    var actual=controller.getGuestType();

    var expected=db.GuestTypes.ToList();

    Assert.Equal(actual,expected);
}
  • Related