Here is my controller method
[HttpPost]
[Authorize]
[Route(RouteConfig.Routes.LovList.contactStatus)]
public IHttpActionResult ContactStatusList()
{
try
{
var result = new DBClass.HeroDb().GetList(
DBClass.DBConstants.ListConstants.query_Contact_Status);
return Json(new Models.Response(
Models.ResponseMessages.Success,
result)
);
}
catch(System.Data.SqlClient.SqlException)
{
return InternalServerError();
}
catch(System.Exception ex)
{
Logger.Error(ex, ex.Message, ex.StackTrace);
return InternalServerError();
}
}
and this is my test case method
[TestMethod()]
public void ContactStatusListTest()
{
Mock<DBClass.HeroDb> mock = new Mock<DBClass.HeroDb>();
mock.Setup(x => x.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status))
.Returns(CreateContactList());
var result = new ListController().ContactStatusList();
Models.Response response = (Models.Response)result;
Assert.AreEqual(response.Message, Models.ResponseMessages.Success);
Assert.IsNotNull(response.Data);
}
public System.Data.DataTable CreateContactList()
{
DataTable table = new DataTable();
table.Columns.Add("ContactStatus");
DataRow row1 = table.NewRow();row1["ContactStatus"] = "Contacted"; table.Rows.Add(row1);
DataRow row2 = table.NewRow(); row2["ContactStatus"] = "Not Contacted"; table.Rows.Add(row2);
DataRow row3 = table.NewRow(); row3["ContactStatus"] = "Contacted"; table.Rows.Add(row3);
return table;
}
I tried to mock GetList() function in my test method but it is not working. Controller method is giving an Internal server error . Because conrol is going to
var result = new DBClass.HeroDb()
.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status);
this line and db object is null here. Please help as i am beginner in unit test case building .
First of all let's set the basis first: unit testing is different than integration testing .
In that case this is a unit test on the controller's method ContactStatusList
. You're testing only this method and you actually did things correctly by trying to mock your HeroDb
object. Note that you decided to mock this object because this is a dependency .
The problem is you set up the Mock but you don't use it because in your ContactStatusList
method you call new DBClass.HeroDb()
.
There's a 2nd problem is that you're trying to mock a class. This is actually possible but all the class's methods you want to mock must be declared as virtual. Therefore it's actually better to mock an interface instead.
This interface should be received in the constructor of your ListController
. On a regular execution of your Web Project inject an instance of that interface in the startup but in unit tests feed your mock to the ListController
's constructor.
Remember this rule: Any dependency should be received by your controller's constructor
Here is your interface and your DbHero class
public interface IDbHero
{
IEnumerable<Contact> GetList(QueryContactStatus status);
}
public class DbHero : IDbHero
{
public IEnumerable<Contact> GetList(QueryContactStatus status)
{
// Implementation here
}
}
Now here's your controller:
[ApiController]
[Route("api/[controller]")]
public class ListController: ControllerBase
{
private readonly IHeroDb _heroDb;
public ListController(IHeroDb heroDb)
{
_heroDb = heroDb ?? throw new ArgumentNullException(nameof(heroDb));
}
[HttpPost]
[Authorize]
[Route(RouteConfig.Routes.LovList.contactStatus)]
public IHttpActionResult ContactStatusList()
{
try
{
var result = _heroDb.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status);
return Json(new Models.Response(
Models.ResponseMessages.Success,
result)
);
}
catch(System.Exception ex)
{
Logger.Error(ex, ex.Message, ex.StackTrace);
throw;
}
}
}
Note that I removed the block where you only catch the SqlException
because anyway if you've an unhandled exception the server will return an internal server error so it's useless to catch it if you don't even log the error. Also in the 2nd catch block I just throw
so the server will also automatically return an internal server error. If you're in debug mode this could be handy as you'd get the full Exception returned to you but if you return InternalServerError()
you'd get no information even in debug and you'd have to check the logs...
In the ConfigureServices
method of your Startup.cs
class, inject your implementation of the IDbHero
interface. Note this is a scoped service, meaning a new instance will be created for each HTTP request. Personally I never inject my Database access layer as a singleton because this could lead to some issues depending of the way this layer is implemented. For exemple EF Core's DbContext is incompatible with a singleton pattern.
services.AddScoped<IDbHero>(_ => new DBClass.HeroDb(Configuration.GetConnectionString("DbHeroConnectionString")));
I don't know how you handle the connection with the database because there's no mention of the connection string in your code example but I would do something like above.
Your connection string is coming from your appsettings.json
config file
"ConnectionStrings": {
"DbHeroConnectionString": "YourConnectionString"
}
Now to use your mocked object in your unit test just do like this:
[TestMethod()]
public void ContactStatusList_ShouldReturnData_WhenCalled()
{
// ARRANGE
var mock = new Mock<IHeroDb>();
mock.Setup(x => x.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status))
.Returns(CreateContactList());
var sut = new ListController(mock.Object);
// ACT
var result = sut.ContactStatusList();
// ASSERT
Models.Response response = (Models.Response)result;
Assert.AreEqual(response.Message, Models.ResponseMessages.Success);
Assert.IsNotNull(response.Data);
}
Notice few things here:
Your unit test name: This should show 3 things:
You can for exemple test that a method is returning an error when parameters have incorrect values. This should tested in another unit test
Unit tests are always in 3 parts ARRANGE
, ACT
and ASSERT
. It's always a good practice to write that in each test so you can better organize your code
sut
means System Under Test
: this is what you want to test, all other dependencies (like your DbHero
layer) should be mocked.
Now the next step would be to write a unit test to test your DbHero.GetList
. This time you'll create a real instance (not a mock) of the DbHero
class because this is what you want to test: this is your sut .
Note that I've an intermediate level in testing so what I'm showing you is good practices I've learnt from my coworkers. But maybe some more experience developers could come up with better practices than mine.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.