How to unit test a Web API controller using XUnit - c#

I am trying to unit test a method within my controller in my Web API using XUnit. The role of the method is to get a single title, by ISBN, from the database. The issue I came across during unit testing is that I am unsure how to insert the dummy data that I must perform the test on, as well as how the Assert function works.
TitleController.cs
[ApiController]
[Route("titlecontroller")]
public class TitleController : Controller
{
private IGtlTitleRepository _gtlTitleRepository;
public TitleController(IGtlTitleRepository gtlTitleRepository)
{
_gtlTitleRepository = gtlTitleRepository;
}
[Route("getTitle/{ISBN}")]
[HttpGet()]
public GtlTitle GetTitle(string ISBN)
{
return _gtlTitleRepository.GetTitle(ISBN);
}
}
IGtlTitleRepository.cs
public interface IGtlTitleRepository
{
GtlTitle GetTitle(string ISBN);
}
MockGtlTitleRepository.cs
public class MockGtlTitleRepository : IGtlTitleRepository
{
private readonly string _connection;
public MockGtlTitleRepository(IOptions<ConnectionStringList> connectionStrings)
{
_connection = connectionStrings.Value.GTLDatabase;
}
private List<GtlTitle> _titleList;
public GtlTitle GetTitle(string ISBN)
{
using (var connection = new SqlConnection(_connection))
{
connection.Open();
return connection.QuerySingle<GtlTitle>("GetTitleByISBN", new { ISBN }, commandType: CommandType.StoredProcedure);
}
}
}
Right, as for my test code, I was able to write the following code, but as I said above, I can't figure out a proper way to test the method.
public class UnitTest1
{
[Fact]
public void Test1()
{
var repositoryMock = new Mock<IGtlTitleRepository>();
var title = new GtlTitle();
repositoryMock.Setup(r => r.GetTitle("978-0-10074-5")).Returns(title);
var controller = new TitleController(repositoryMock.Object);
var result = controller.GetTitle("978-0-10074-5");
// assert??
repositoryMock.VerifyAll();
}
}
What should be done within this unit test in order to properly test the method?
EDIT:
GtlTitle.cs
public class GtlTitle
{
public string ISBN { get; set; }
public string VolumeName { get; set; }
public string TitleDescription { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PublisherName { get; set; }
}

Before going to testing, there are a few things I recommend updating in your code:
Make your repository methods and controller actions async (thus web server can process requests while waiting for database roundtrips for previous calls)
Use ActionResult as an action return type. This way you can send different http status codes to the client.
Return 404 NotFound status code when title not found instead of returning successful result with null as payload.
Consider using a RESTful approach for API endpoints. E.g. base uri for titles resource should be something like api/titles
Don't specify getTitle for getting title endpoint, because you know HTTP verb which endpoint is mapped to (GET) and base resource url (api/titles).
With these notes applied:
[ApiController]
[Route("api/titles")]
public class TitleController : Controller
{
private IGtlTitleRepository _gtlTitleRepository;
public TitleController(IGtlTitleRepository gtlTitleRepository)
{
_gtlTitleRepository = gtlTitleRepository;
}
[HttpGet("{ISBN}")] // GET api/titles/{ISBN}
public async Task<ActionResult<GtlTitle>> GetTitle(string ISBN)
{
var title = await _gtlTitleRepository.GetTitle(ISBN);
if (title == null)
return NotFound();
return title;
}
}
Testing successful title retrieving:
[Fact]
public async Task Should_Return_Title_When_Title_Found()
{
var repositoryMock = new Mock<IGtlTitleRepository>();
var title = new GtlTitle();
repositoryMock.Setup(r => r.Get("978-0-10074-5")).Returns(Task.FromResult(title));
var controller = new TitleController(repositoryMock.Object);
var result = await controller.GetTitle("978-0-10074-5");
Assert.Equal(title, result.Value);
}
When title not found:
[Fact]
public async Task Should_Return_404_When_Title_Not_Found()
{
var repositoryMock = new Mock<IGtlTitleRepository>();
repositoryMock.Setup(r => r.Get("978-0-10074-5")).Returns(Task.FromResult<GtlTitle>(null));
var controller = new TitleController(repositoryMock.Object);
var result = await controller.GetTitle("978-0-10074-5");
Assert.IsType<NotFoundResult>(result.Result);
}

Related

ASP.NET Core Web API - Third Party API not giving expected result

In my ASP.NET Core-6 Web API, I am given a third party API to consume and then return the account details. I am using WebClient.
api:
https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber=112123412
Headers:
X-GivenID:Given2211
X-GivenName:Givenyou
X-GivenPassword:Given#llcool
Then JSON Result is shown below:
{
"AccountName": "string",
"CurrentBalance": 0,
"AvailableBalance": 0,
"Currency": "string"
}
So far, I have done this:
BalanceEnquiryResponse:
public class BalanceEnquiryResponse
{
public string Response
{
get;
set;
}
public bool IsSuccessful
{
get;
set;
}
public List<BalanceList> AccountBalances
{
get;
set;
}
}
BalanceList:
public class BalanceList
{
public string AccountNumber
{
get;
set;
}
public decimal CurrentBalance
{
get;
set;
}
public decimal AvailableBalance
{
get;
set;
}
public string Currency
{
get;
set;
}
}
Then the service is shown below.
IDataService:
public interface IDataService
{
BalanceEnquiryResponse GetAccountBalance(string accountNo);
}
public class DataService : IDataService
{
private readonly ILogger<DataService> _logger;
private readonly HttpClient _myClient;
public DataService(ILogger<DataService> logger, HttpClient myClient)
{
_logger = logger;
_myClient = myClient;
PrepareAPIHeaders(); // Actually apply the headers!
}
private void PrepareAPIHeaders()
{
_myClient.DefaultRequestHeaders.Add("X-GivenID", "Given2211");
_myClient.DefaultRequestHeaders.Add("X-GivenName", "Givenyou");
_myClient.DefaultRequestHeaders.Add("X-GivenPassword", "Given#llcool");
_myClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
_myClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json; charset=utf-8");
}
// If you want to use async API, you need to go async all the way.
// So make this Method async, too!
public async Task<BalanceEnquiryResponse> GetAccountBalance(string accountNo)
{
_logger.LogInformation("Accessing Own Account");
var url = $"https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber={accountNo}";
var response = await _myClient.GetAsync(url);
// vv Get your payload out of the Http Response.
var responseResults = await response.Content.ReadAsAsync<BalanceEnquiryResponse>();
return responseResults;
}
}
I tested the third party api with the headers on POOSTMAN:
https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber=112123412
and it gives me expected result. But from my code, when I tried to call GetAccountBalance from the code below and I supplied model.account_number:
public async Task<BaseResponse> FinalResult(RequestDto model)
{
var response = new BaseResponse();
try
{
//Check account Balance
var accBalance = _dataAccess.GetAccountBalance(model.account_number);
if (!accBalance.IsSuccessful)
{
response.response_code = "";
response.response_description = "Could not fetch account for subscriber";
return response;
}
}
}
I got this error in:
response.response_description = "Could not fetch account for subscriber";
What am I doing wrongly, especially in public class DataService and how do I resolve it?
Thanks.
It might just be that we talking about a GetCall but sometimes i had problems with headers which are set directly on the client layer. So i would try to set them on the request, instead of the client.
I wrote this without a editor so i could not test it.
But you should get the gist of what im trying to do
public async Task<BalanceEnquiryResponse> GetAccountBalance(string accountNo)
{
_logger.LogInformation("Accessing Own Account");
var url = $"https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber={accountNo}";
using(var request = new HttpRequestMessage(HttpMethod.Get, url))
{
//add headers to the request not the client
PrepareAPIHeaders(request);
var result = await _myClient.SendAsync(request);
result.EnsureSuccessStatusCode();
/*Read response and parse it into an object and return*/
}
return null;
}
private void PrepareAPIHeaders(HttpRequestMessage request)
{
request.Headers.Add("X-GivenID", "Given2211");
/*Add other Headers*/
}

How to unit test service method with Moq

I tried to write unit test for getMark() and faced problem with Moq, with which I'm not familiar. I have no idea what method and object properly mock in order to unit test getMark()
Here is my MarkServiceClass containing getMark()
public class MarkService : IMarkService
{
IMarkService _markService;
IStdService _stdService;
IStdService _stdMService;
RClass cs;
public MarkService(IMarkService markService, IStdService stdService, IStdService stdMService)
{
_markService = markService;
_stdService = stdService;
_stdMService = stdMService;
}
public bool Init(int sID, string pID, string year)
{
try
{
cs = new RClass ();
cs.sLevel = __stdService.GetAsIQueryable().FirstOrDefault(x => x.UID == pID);
var mInfo = __stdMService.GetSTDM(sID, pID, year);
cs.Type = mInfo.CalculateAmount;
return true;
}
catch
{
return false;
}
}
public MarkVM getMark(int sID, string pID, string year)
{
var output=Init(sID, pID, year);
if (!output)
return null;
int sGrade= 0;
int sMark= 0;
//here are conditions where sGrade and sMark used
return new MarkVM
{
Grade = sGrade,
Mark = sMark
};
}
}
and MarkVM
public class MarkVM
{
public int Grade { get; set; }
public int Mark { get; set; }
}
The code you shared is not complete so I had to make some assumptions to give you an example how to unit test getMark
public class MarkVM
{
public int Grade { get; set; }
public int Mark { get; set; }
}
Not knowing what RClass is, I define it with minimal requirements
public class RClass {
public String Uid { get; set; }
public string sLevel { get; set; }
public int Type { get; set; }
}
Same for this Info your service retrieves with GetSTDM
public class Info
{
public int CalculateAmount { get; set; }
}
Now come the interfaces. This is definitely required if you want to mock
public interface IStdService
{
List<RClass> GetAsIQueryable();
Info GetSTDM(int sID, string pID, string year);
}
Those 2 methods are the ones you'll want to mock if you unit test getMark.
Mocking getMark itself will only allow you to check it is called, but not its behavior which is the purpose of unit testing.
Now the main class. I removed the injection of IMarkService in the constructor because I really don't see why you would do that: Markservice implements IMarkService here.
For any reason you use 2 instances of IStdService, I kelpt that but then you need to inject it too.
public class MarkService : IMarkService
{
private IStdService __stdService;
private IStdService __stdMService;
public RClass cs;
public MarkService(IStdService stdMService, IStdService stdService)
{
__stdMService = stdMService;
__stdService = stdService;
}
public bool Init(int sID, string pID, string year)
{
try
{
cs = new RClass();
cs.sLevel = __stdService.GetAsIQueryable().FirstOrDefault(x => x.Uid == pID).sLevel;
var mInfo = __stdMService.GetSTDM(sID, pID, year);
cs.Type = mInfo.CalculateAmount;
return true;
}
catch
{
return false;
}
}
public MarkVM getMark(int sID, string pID, string year)
{
var output = Init(sID, pID, year);
if (!output)
return null;
int sGrade = 0;
int sMark = 0;
//here are conditions where sGrade and sMark used
return new MarkVM
{
Grade = sGrade,
Mark = sMark
};
}
}
Now comes the test. If you want to unit test getMark you could either mock Init from IMarkService, or consider the behavior comes from this Init and then you want to mock GetAsIQueryable and GetSTDM.
I made the assumption second option is what you want.
using System.Collections.Generic;
using MarkServiceNS;
using Moq;// Moq framework where you'll find everything you need
using NUnit.Framework;// Using NUnit for unit test. Because I like it :-)
using NUnit.Framework.Constraints;
namespace UnitTestWithMoqExample
{
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void getMark()
{
var mockedStdService = new Mock<IStdService>();
mockedStdService.Setup(x => x.GetAsIQueryable())
.Returns(new List<RClass> { new RClass { Uid = "uid", sLevel = "expected", Type = 1 } }); // Here you define what it the mocked result of GetAsIQueryable call.
var mockedStdMService = new Mock<IStdService>();
mockedStdMService.Setup(x => x.GetSTDM(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(new Info { CalculateAmount = 1 });// Same here. You mock GetSTDM. The method parameters are not expected to change the behavior in my unit test, this is why I consider It.Any<T> so whatever you pass to the mock, the result will be the same.
// Here is the assertion. This should do the job
var service = new MarkServiceNS.MarkService(mockedStdMService.Object, mockedStdService.Object);
Assert.IsNotNull(service.getMark(1, "", ""));
Assert.IsInstanceOf(typeof(MarkVM), service.getMark(1, "", ""));
Assert.AreEqual(0, service.getMark(1, "", "").Grade);
Assert.AreEqual(0, service.getMark(1, "", "").Mark);
}
}
}
A basic Moq coding will be like this
[Test]
public void Test1()
{
var mock = new Mock<IMarkService>();
mock.Setup(p => p.getMark(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>())).Returns(new MarkVM());
mock.Verify(p => p.getMark(1001, "P001", "2022"), Times.Once());
}
I am posting this as an example as I don't have your full code
Use the above technique to moq your methods GetAsIQueryable and GetSTDM and CalculateAmount
And call the method Init and then call the getMark

How to unit test Command Handler in CQRS pattern in C#

I'm learning and practicing CQRS and Unit Test in C#. I need to know how to unit test Command Handlers in CQRS.
Here is my CreateAuthorCommand:
public sealed class CreateAuthorCommand : ICommand<Author>
{
public CreateAuthorCommand(string firstName, string lastName, DateTimeOffset dateOfBirth, string mainCategory, IEnumerable<CreateBookDto> books)
{
FirstName = firstName;
LastName = lastName;
DateOfBirth = dateOfBirth;
MainCategory = mainCategory;
Books = books;
}
public string FirstName { get; }
public string LastName { get; }
public DateTimeOffset DateOfBirth { get; private set; }
public string MainCategory { get; }
public IEnumerable<CreateBookDto> Books { get; }
[AuditLog]
[DatabaseRetry]
internal sealed class AddCommandHandler : ICommandHandler<CreateAuthorCommand, Author>
{
private readonly IUnitOfWork _unitOfWork;
public AddCommandHandler(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
}
public async Task<Result<Author>> Handle(CreateAuthorCommand command)
{
var nameResult = Name.Create(command.FirstName, command.LastName);
var birthDateResult = BirthDate.Create(command.DateOfBirth);
var mainCategoryResult = Entities.Authors.MainCategory.Create(command.MainCategory);
var authorResult = Result.Combine(nameResult, birthDateResult, mainCategoryResult)
.Map(() => new Author(nameResult.Value, birthDateResult.Value, null, mainCategoryResult.Value));
if (authorResult.IsFailure)
return Result.Failure<Author>(authorResult.Error);
await _unitOfWork.AuthorRepository.AddAsync(authorResult.Value);
await _unitOfWork.SaveChangesAsync();
return Result.Success(authorResult.Value);
}
}
}
Here is my Unit Test:
public class CreateAuthorCommandTests
{
[Fact]
public void Create_Author_Should_Call_Add_Method_Once()
{
var fixture = new Fixture();
var command = fixture.Create<CreateAuthorCommand>();
var mockUnitOfWork = new Mock<IUnitOfWork>();
var mockHandler = new Mock<ICommandHandler<CreateAuthorCommand, Author>>();
var result = mockHandler.Object.Handle(command);
mockUnitOfWork.Verify(x => x.AuthorRepository.AddAsync(It.IsAny<Author>()), Times.Once);
}
}
When I debug the above test,
I'm not able to step into Handler Method!!
How to pass mockUnitOfWork as constructor parameter to AddCommandHandler?
If I can pass mockUnitOfWork, the I can verify the AddAsync call inside my AuthorRepository. Please assist on what I'm doing wrong.
You are testing the handler, so instead of this
var result = mockHandler.Object.Handle(command);
...create an actual instance of AddCommandHandler and inject the dependencies it requires, i.e.
var mockUnitOfWork = new Mock<IUnitOfWork>();
var handler = new AddCommandHandler(mockUnitOfWork.Object);
When you call in to Handle, you'll now be stepping into your implementation.
If you want to keep the Internal access modifier, you can use InternalsVisibleTo attribute to grant access to your test project, e.g. do this in your project that defines the Handler,
[assembly: InternalsVisibleTo(“UnitTests”)]
https://anthonygiretti.com/2018/06/27/how-to-unit-test-internal-classes-in-net-core-applications/

Parse FHIR Patient response to json

I'm new to .Net and FHIR. I followed a few tutorial to understand how FHIR API works. I need to create an application that use only GET request to get data from server. Below, I am trying to make a request to retrieve a patient by ID in PatientRepository class. However, when I test it with Postman, it doesn't return any response. How should I change my code? Many thanks
Model:
public class Patient : Hl7.Fhir.Model.Patient
{
public string name { get; set; }
public string birthday { get; set; }
}
public class PatientList
{
public List<Patient> Patients { get; set; }
}
Controller:
public class PatientController : Controller
{
private readonly IPatientRepository _patientRepository;
public PatientController(IPatientRepository patientRepository)
{
_patientRepository = patientRepository;
}
[HttpGet]
[Route("api/GetPatientById/{id}")]
public IActionResult getPatientById(long id)
{
var model = _patientRepository.GetPatientById(id);
if (model == null)
return NotFound();
return Ok(model);
}
}
}
PatientRepository:
public class PatientRepository : IPatientRepository
{
public async Task<Patient> GetPatientById(long id)
{
var client = new FhirClient("https://fhir.****.***/hapi-fhir-jpaserver/fhir/");
client.Timeout = (60 * 100);
client.PreferredFormat = ResourceFormat.Json;
var pat = client.Read<Patient>("Patient/1");
var parser = new FhirJsonParser();
return new Patient
{
birthday = pat.birthday,
}
}
}

Mocking non virtual posted model properties and method

I have person model class defined as:
public class PersonModel
{
public bool SelectionSubmitted = false;
public bool ShowValidationSummary = false;
public string Name;
public string Get()
{
//actual implementation return some value from the db
return string.Empty;
}
}
The controller implementation is as follows:
class HomeController : Controller
{
[HttpGet]
public ActionResult Index(PersonModel model)
{
if (model.SelectionSubmitted && !ValidateSelections(model))
{
model.ShowValidationSummary = true;
}
return View("Index", model.Get());
}
private bool ValidateSelections(PersonModel model)
{
if(model.Name == "")
{
ModelState.AddModelError("EmptyPersonName", "Person name cannot be null");
}
return ModelState.IsValid;
}
}
The test class and method is defined as:
[TestClass]
public class ChildWithoutPlacementControllerTest
{
private readonly Mock<PersonModel> _mockPersonModel;
public ChildWithoutPlacementControllerTest()
{
_mockPersonModel = new Mock<PersonModel>();
}
[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown()
{
//Arrange
HomeController controller = new HomeController();
_mockPersonModel.Setup(x => x.Get()).Returns(It.IsAny<string>());
_mockPersonModel.SetupGet(x => x.Name).Returns(string.Empty);
_mockPersonModel.SetupGet(x => x.SelectionSubmitted).Returns(true);
//Act
controller.Index(_mockPersonModel.Object);
//Assert
var isShowSummarySetToTrue = _mockPersonModel.Object.ShowValidationSummary;
Assert.IsTrue(isShowSummarySetToTrue);
}
}
What I want to achieve is mock the SelectionSubmitted and Name property to true and string.Empty respectively also Setup the Get method of PersonModel class, and check if the test return object has ShowValidationSummary set to true.
However, I am getting that I can't set up the non-virtual property Name.
Am I doing something wrong or is there any way to do it without changing the implementation code?
Am I doing something wrong
This appears to be an XY problem.
is there any way to do it without changing the implementation code
There really is no need for moq in this scenario. You can use inheritance to craft a fake model to be used in the test. The fake model will override the method that is tightly coupled to the database. (more on that later)
public class FakePerson : PersonModel {
public new string Get() {
return string.Empty; //Not calling the base Get
}
}
The test can then be refactored to use the fake model and be exercised to completion as intended.
[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown() {
//Arrange
var fakePersonModel = new FakePerson() {
Name = string.Empty,
SelectionSubmitted = true
};
var controller = new HomeController();
//Act
controller.Index(fakePersonModel);
//Assert
var isShowSummarySetToTrue = fakePersonModel.ShowValidationSummary;
Assert.IsTrue(isShowSummarySetToTrue);
}
That aside, your model appears to be doing to much if the actual Get implementation does as you stated here
actual implementation return some value from the db
Consider refactoring that functionality out into a service (Single Responsibility Principle / Separation of Concerns)
public interface IPersonModelService {
string Get(PersonModel person);
}
public class PersonModelService : IPersonModelService {
public string Get(PersonModel person) {
//actual implementation return some value from the db
}
}
and keep the model as lean as possible. Also consider refactoring those public fields into public properties.
public class PersonModel {
public bool SelectionSubmitted { get; set; }
public bool ShowValidationSummary { get; set; }
public string Name { get; set; }
}
The controller would depend on the service abstraction
class HomeController : Controller {
private IPersonModelService service;
public HomeController(IPersonModelService service) {
this.service = service;
}
[HttpGet]
public ActionResult Index(PersonModel model) {
if (model.SelectionSubmitted && !ValidateSelections(model)) {
model.ShowValidationSummary = true;
}
return View("Index", service.Get(model));
}
private bool ValidateSelections(PersonModel model) {
if (model.Name == "") {
ModelState.AddModelError("EmptyPersonName", "Person name cannot be null");
}
return ModelState.IsValid;
}
}
And now the test can be exercised to completion in isolation.
[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown() {
//Arrange
var model = new PersonModel() {
Name = string.Empty,
SelectionSubmitted = true
};
var serviceMock = new Mock<IPersonModelService>();
serviceMock.Setup(_ => _.Get(It.IsAny<PersonModel>())).Returns(string.Empty);
var controller = new HomeController(serviceMock.Object);
//Act
controller.Index(model);
//Assert
var isShowSummarySetToTrue = model.ShowValidationSummary;
Assert.IsTrue(isShowSummarySetToTrue);
}

Categories

Resources