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 yourHeroDb
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 callnew 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 theListController
's constructor.Remember this rule: Any dependency should be received by your controller's constructor
Here is your interface and your DbHero class
Now here's your controller:
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 justthrow
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 returnInternalServerError()
you'd get no information even in debug and you'd have to check the logs...In the
ConfigureServices
method of yourStartup.cs
class, inject your implementation of theIDbHero
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.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 fileNow to use your mocked object in your unit test just do like this:
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
andASSERT
. It's always a good practice to write that in each test so you can better organize your codesut
meansSystem Under Test
: this is what you want to test, all other dependencies (like yourDbHero
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 theDbHero
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.