Unit Testing a Controller method returns null - c#

I'm trying to learn Unit testing in .NET 6 by testing a controller function GetProduct. The problem is I get null returned in the variable var product = await _productController.GetProduct(productId);. As you can see in the picture below, the Result is ok but the Value, where the ServiceResponse<Product> was suppose to be is null.
Here is the controller function:
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
[HttpGet("{productId}")]
public async Task<ActionResult<ServiceResponse<Product>>> GetProduct(int productId)
{
var result = await _productService.GetProductAsync(productId);
return Ok(result);
}
}
Here is the Test:
public class ProductControllerTest
{
private readonly ProductController _productController;
private readonly Mock<IProductService> _productService = new Mock<IProductService>();
public ProductControllerTest()
{
_productController = new ProductController(_productService.Object);
}
[Test]
public async Task GetProducts_ReturnsProduct_IfProductExists()
{
//Arange
var productId = 1;
var prodData = new Product
{
Id = productId,
Title = "null"
};
var prductResponse = new ServiceResponse<Product>
{
Data = prodData,
Success = true ,
Message = ""
};
_productService.Setup(x => x.GetProductAsync(productId)).ReturnsAsync(prductResponse);
//Act
var product = await _productController.GetProduct(productId);
//Assert
Assert.That(product?.Value?.Data?.Id, Is.EqualTo(productId));
}
}

This behavior is observed due to overloaded operators on ActionResult<T> class.
Since the method, OK(value) returns an instance of OKObjectResult and the return type of controller method is of type ActionResult<ServiceResponse<Product>> the returned OKObjectResult instance is wrapped in an instance of ActionResult<T> and is exposed by the Result property. Hence typecasting the Result property (as shown by #BennyM) to OKObjectResult works.
Please note that the assertion would have succeeded had the controller method returned the ServiceResponse<Product> directly without modifying the return type on controller's method.
While this explains the behavior, I personally feel there is a better way to test controllers. This MSDN Article explains about integration testing. One can effectively unit test all the dimensions of the controllers - authentication, validation, (de)serialization, etc - by mocking the immediate dependencies of the respective controllers.

Since you are returning with an Ok call in your controller you can add a cast to the unit test.
var result = (await _productController.GetProduct(productId)).Result as OkObjectResult;
Assert.IsNotNull(result);
var returnedServiceResponse = result.Value as ServiceResponse<Product>;
Assert.That(returnedServiceResponse ?.Data?.Id, Is.EqualTo(productId));
Also you don't have to use Actionresult. You can also just return your service response
[HttpGet("{productId}")]
public async Task<ServiceResponse<Product>> GetProduct(int productId)
{
var result = await _productService.GetProductAsync(productId);
return result;
}
This will also make the test a bit easier as no need to use the OkObjectResult.

Related

How to write unit tests for asp.net core web api controller

So I am very new to writing tests. I created an ASP.NET core web api along with angular. I have to write unit tests for the web API controllers. I have been reading Microsoft documentation on how to get started with unit tests of ASP.NET web APIs. But I am still very unsure on how to go about writing proper tests.
My Controller Code
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
private readonly IOrderRepository _repo;
private readonly IMapper _mapper;
public OrdersController(IOrderRepository repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
[AllowAnonymous]
[HttpPost()]
public async Task<IActionResult> AddOrder(OrderForMappingDto orderForMappingDto)
{
//if(orderForMappingDto.ARentalOrNot == null)
//{
// throw new Exception("Value can't be left null");
//}
var orderToCreate = _mapper.Map<TblOrder>(orderForMappingDto);
var createdOrder = await _repo.AddOrder(orderToCreate);
return Ok(createdOrder);
}
}
My Repository Code
public class OrderRepository : IOrderRepository
{
private readonly MovieRentalDBContext _context;
public OrderRepository(MovieRentalDBContext context)
{
_context = context;
}
public async Task<TblOrder> AddOrder(TblOrder tblOrder)
{
await _context.TblOrder.AddAsync(tblOrder);
await _context.SaveChangesAsync();
return tblOrder;
}
}
I understand there is a lot of mocking to be done. But do I need to mock the Entity Framework as well?
I wrote a simple test file.
public void PostsAorder_WhenCalled_ReturnsOkWithResponse()
{
var mockOrderRepository = new Mock<IOrderRepository>();
var mockOrderMapper = new Mock<IMapper>();
var orderControllerObject = new OrdersController(mockOrderRepository.Object, mockOrderMapper.Object);
Task<IActionResult> contentResult = orderControllerObject.AddOrder(new OrderForMappingDto
{
ACustomerId = 3,
AMovieId = 18,
ARentalOrNot = false,
AOrderedDate = DateTime.Now
}) ;
//var contentResult = actionResult as OkNegotiatedContentResult<OrderForMappingDto>;
Assert.IsNotNull(contentResult);
Assert.IsNotNull(contentResult.Result);
}
The OkNegotioatedContent function doesn't work with Tasks. How do I go about using that for task. Also, the tests is passing even when I don't supply the last 3 arguments even though in the DTO they are classified as [Required]. Can somebody help on how to modify the test properly.
mapper Configuration-
Your test is almost okay. First the fixed version then some explanation:
[Fact]
public async Task GivenAValidOrder_WhenICallTheAsOrder_ThenItReturnsOkWithResponse()
{
//Arrange
var mockOrderMapper = new Mock<IMapper>();
mockOrderMapper.Setup(mapper => mapper.Map<TblOrder>(It.IsAny<OrderForMappingDto>()))
.Returns(new TblOrder());
var mockOrderRepository = new Mock<IOrderRepository>();
mockOrderRepository.Setup(repo => repo.AddOrder(It.IsAny<TblOrder>()))
.ReturnsAsync((TblOrder order) => order);
var SUT = new OrdersController(mockOrderRepository.Object, mockOrderMapper.Object);
//Act
var contentResult = await SUT.AddOrder(new OrderForMappingDto
{
ACustomerId = 3,
AMovieId = 18,
ARentalOrNot = false,
AOrderedDate = DateTime.Now
});
//Assert
Assert.NotNull(contentResult);
Assert.IsAssignableFrom<OkObjectResult>(contentResult);
var result = ((OkObjectResult)contentResult).Value;
Assert.NotNull(result);
Assert.IsAssignableFrom<TblOrder>(result);
}
The name of test follows the Given When Then structure to make it easier to understand that under what circumstances how should the controller's action behave.
The test is now asynchronous because we need to await the controller's action to finish in order to examine its result.
I've added the Arrange Act Assert comments to the code in order to emphasize which phase starts when.
I've set the mapper mock to return a new TblOrder and the repo mock to return whatever it receives.
I've renamed orderControllerObject to SUT, because it emphasize which component is under examination (System Under Test).
In the result verification I've used IsAssingableForm instead of IsType because it checks against derived classes as well. In this particular case it is not mandatory, but it is a good practice.
The result type will be OkObjectResult not OkNegotiatedContentResult, so you should check against that.
And finally I've added an extra check to make sure that the returned object's type is that what is expected.

How do you moq a class for unit test

This question is posted as a follow up to How do you Mock an class for a unit test that has a return type but no input parameters
Since asking the original question I have now created a Minimal, Complete and Verifiable Example which is used as the basis for this question.
I have a controller (shown below)
public class HomeController : Controller
{
private OrganisationLogic _organisationLogic;
public HomeController(OrganisationLogic logic)
{
_organisationLogic = new OrganisationLogic();
}
public ActionResult Index()
{
var model = _organisationLogic.GetOrganisation().ToViewModel();
return View(model);
}
}
The controller retrieves data from a method in a Business Logic Layer called OrganisationLogic (shown below)
public class OrganisationLogic : LogicRepository<OrganisationModel>
{
public OrganisationLogic()
{
}
public override OrganisationModel GetOrganisation()
{
return new OrganisationModel { Id = 1, OrganisationName = "My Orgaisation", Address = "Logic" };
}
}
The business logic later inherits Logic repository (shown below)
public abstract class LogicRepository<T> : ILogicRepository<T>
{
protected LogicRepository()
{
}
public abstract T GetOrganisation();
}
The Logic Repository implements the ILogicRepository Interface (shown below)
public interface ILogicRepository<TModel>
{
TModel GetOrganisation();
}
I want to Unit Test the HomeController to verify that the data displayed in the ViewModel is returned correctly from OrganisationLogic and Transformed from OrganisationModel into an OrganisationViewModel.
I have written the following UnitTest which uses Moq to mock the method _OrganisationLogic.GetOrganisation().
[TestMethod]
public void Index()
{
var _OrganisationLogic = new Mock<OrganisationLogic>();
var testdata = new OrganisationModel { Id = 3, OrganisationName = "My Test Class Organisation" };
_OrganisationLogic.Setup(p => p.GetOrganisation()).Returns(testdata).Callback<OrganisationModel>(p=>p = testdata);
HomeController controller = new HomeController(_OrganisationLogic.Object);
ViewResult result = controller.Index() as ViewResult;
OrganisationViewModel model = (OrganisationViewModel)result.Model;
Assert.AreEqual(testdata.OrganisationName,model.OrganisationName);
}
When I run the test the test fails. The reason for this is that the Mock has not overridden the class and has instead returned the result from the actual method in the BusinessLogic Layer.
In my original question I posted that the error message being generated was:
System.ArgumentException Invalid callback. Setup on method with 0 parameter(s) cannot invoke callback with different number of parameters (1). Source=Moq StackTrace: at Moq.MethodCallReturn2.ValidateNumberOfCallbackParameters(MethodInfo
callbackMethod) at Moq.MethodCallReturn2.ValidateReturnDelegate(Delegate callback) at Moq.MethodCallReturn2.Returns[T](Func2 valueExpression)
I have now been able to replicate this error message exactly by running the following Unit Test. I suspect the unit test above is closer to what I need and that in the instance below I am setting up the Return() incorrectly. Thoughts on this are welcome?
[TestMethod]
public void Index()
{
var _OrganisationLogic = new Mock<OrganisationLogic>();
var testdata = new OrganisationModel { Id = 3, OrganisationName = "My Test Class Organisation" };
_OrganisationLogic.Setup(p => p.GetOrganisation()).Returns<OrganisationModel>(p=>p = testdata).Callback<OrganisationModel>(p=>p = testdata);
HomeController controller = new HomeController(_OrganisationLogic.Object);
ViewResult result = controller.Index() as ViewResult;
OrganisationViewModel model = (OrganisationViewModel)result.Model;
Assert.AreEqual(testdata.OrganisationName,model.OrganisationName);
}
My question is how to I setup the Mock so that it uses my test data.
In order to assist with answering the the above I have placed a version of the Code on GitHub that demonstrates this issue and shows the test failing. This can be accessed at https://github.com/stephenwestgarth/UnitTestExample
Any help would be very much appreciated.
Change the constructor of HomeController so that the parameter type is ILogicRepository<OrganisationModel>, and the field will need that type as well, and use the instance that was injected into the constructor. _organisationLogic = logic; (your code above ignores the parameter and creates its own concrete instance of the OrganisationLogic, which means it is not using your mock object.
In the test change the declaration of _OrganisationLogic to be...
var _OrganisationLogic = new Mock<ILogicRepository<OrganisationModel>>();
As I said when you previously asked, I don't think you need that Callback in there.
Edited constructor would look like this...
private ILogicRepository<OrganisationModel> _organisationLogic;
public HomeController(ILogicRepository<OrganisationModel> logic)
{
_organisationLogic = logic;
}

Nunit testing with Mock. Instance of Interface

I have the following (simplified) code.
public class Controller
{
private readonly IService _service;
public Controller(IService service)
{
_service = service;
}
public async Task<IHttpActionResult> Create(MyObject object)
{
var result = _service.method(object);
if (!result.Succeeded)
{
return this.GetErrorResult(object);
}
}
}
and SimpleInjector is used to inject the dependency between _service and its implementation class, like this:
public static void Register(Container container)
{
container.Register<IService, Service>();
}
As a note, injection and unit testing are new to me so I do not fully understand them, but am learning.
If I run the application through Swagger, all is working fine.
As a note, the Register function is called when I run the application through Swagger.
Now, I am trying to setup some unit tests using NUnit, and am Mocking the IService object like this:
var Service = new Mock<IService>();
Controller _controller = new Controller(Service.Object);
_controller.Create(new MyObject object());
which seems to be correct to me so far - although I am not sure?
The problem is that for the unit test, result is always null - I think the is because there is a problem with my Mock of the interface - it does not seem to be finding the method - it never steps into it and does not show up int he debugger.
As a note, for the unit test, the Register method does not get called. I did try calling it to register the dependency, but it does not help.
As I said above, this is all new to me and I am on the edge of my understanding on all of this.
I am out of ideas and do not know where to look from here, so any help would be greatly appreciated.
EDIT:
The original question had the following:
public async Task<IHttpActionResult> Create(string content)
which I have updated to:
public async Task<IHttpActionResult> Create(MyObject object)
Can anyone advise how I can pass in a generic reference to MyObject on the setup, without having to make an instance of this class.
So basically I want to tell it that an instance of this class will be passed in, without creating that instance.
I have tried the following:
Service.Setup(x => x.method(It.IsAny<MyObject>())
but it says cannot convert MethodGroup to MyObject
and here is the definition of IService:
public interface IService
{
IdentityResult method(ApplicationUser user, UserLoginInfo login);
}
You need to configure the Mock object to return something for IService.method as follows:
var Service = new Mock<IService>();
Service.Setup(x => x.method(It.IsAny<string>())
.Returns<string>(str => **whatever result you need**);
With the addition of your actual IService definition, you should change the Setup call to:
Service.Setup(x => x.method(It.IsAny<ApplicationUser>(), It.IsAny<UserLoginInfo>())
.Returns<ApplicationUser, UserLoginInfo>((user, login) => new IdentityResult(true));
The setup method has to be called on the Mock object.
var Service = new Mock<IService>();
Service.Setup(x=>x.method("argument")).Returns(YourReturnObject)
Controller _controller = new Controller(Service.Object);
Using your simplified example
public class Controller
{
private readonly IService _service;
public Controller(IService service)
{
_service = service;
}
public async Task<IHttpActionResult> Create(string content)
{
var result = await _service.method(content);
if (!result.Succeeded)
{
return this.GetErrorResult(result);
}
return Ok();
}
}
Lets assume IService is defined as
public interface IService {
Task<Result> method(string input);
}
public class Result {
public bool Succeeded { get; set; }
}
For the unit test you need to setup the mock to fake the actions wanted for the test
public async Task Controller_Given_Content_Should_Return_Ok() {
//Arrange
var input = "content";
var mockService = new Mock<IService>();
mockService
.Setup(m => m.method(input))
.ReturnAsync(new Result { Succeeded = true });
var _controller = new Controller(mockService.Object);
//Act
var result = await _controller.Create(input);
//Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result,typeof(OkResult));
}
Given that the method under test is asynchronous you would want to setup the test to be asynchronous as well.

When to return IHttpActionResult vs Object

In examples of using the ASP.NET Web API I see two different methods used to return data to the calling jQuery function. The first method returns an object of type Client but I am not sure what the second method is returning.
Method #1 (returns Client object)
public IEnumerable<Client> GetAllClients()
{
using (var context = new PQRSModel.PQRSEntities())
{
context.Configuration.ProxyCreationEnabled = false;
var query = context.Clients.OrderBy(c = c.OrgName);
var customers = query.ToList();
return customers;
}
}
Method #2 (What benefit does IHttpActionResult provide?)
public IHttpActionResult GetClient(int clientId)
{
using (var context = new PQRSModel.PQRSEntities())
{
context.Configuration.ProxyCreationEnabled = false;
var client = context.Clients.FirstOrDefault(c = c.ID == clientId);
if (client == null)
{
return NotFound();
}
return Ok(client);
}
}
If the second method finds a single object is there any reason it could not also return a Client object type?
Returning IHttpActionResult provides a nice separation of concerns.
Your controller can focus on responding to the request in the most sensible manner (status codes, error messages, etc.). Another (service) layer can focus on actually retrieving and transforming the business data.
The side-effect is, your controller methods become more unit testable. Consider the following simple example:
public class MyController : ApiController
{
//or better yet, dependency-inject this
SomeService _service = new SomeService();
public IHttpActionResult Get(int id)
{
if (id < 0)
return BadRequest("Some error message");
var data = _service.GetData(id);
if (data == null)
return NotFound();
return Ok(data);
}
}
Not only is this method's logic understandable just by reading it, but you could now test the logic more easily and naturally, something like (using NUnit syntax):
[TestFixture]
public class MyControllerTests
{
[Test]
public void Get_WithIdLessThan0_ReturnsBadRequest()
{
var controller = new MyController();
int id = -1;
IHttpActionResult actionResult = controller.Get(id);
Assert.IsInstanceOf<BadRequestErrorMessageResult>(actionResult);
}
}
Similarly, you could mock the Service layer and test what happens when you give known id parameters to the controller, etc.
Here is a good article on Unit Testing Controllers in Web Api
The second method allows you to return just status codes (like the 404 in the example), streaming file content and other types of non-object content.

Unit Testing method ASP. NET (NUnit)

I have a controller that need to test. It has a method with the function inside.
public ActionResult GetZZ()
{
ApplyResponseHeaders();
var result = new MediaJsonResult();
using (var str = new StreamReader(Request.InputStream))
{
string inputData = str.ReadToEnd();
MyFunction(inputData, result);
}
return Json(result);
}
I just want to test the function MyFunction. This function is private. How can I do this. Test the entire method is not necessary, because the problems in the appointment of its values ​​in Request.InputStream
Don't try to test private methods ever. These methods are not part of public API and cannot be invoked by caller. Your goal is to satisfy requirements for public API. It really doesn't matter if private method works as expected or not. From caller's point of view this method does not exist and does not have any value.
Instead you should test functionality, which is available via public API, GetZZ() method in your case. But you should mock external dependencies in order to test your controller in isolation with any test data you want.
So, here you have two options. First one is mocking HttpRequest which your controller depends on, and providing test data for input stream (you will have to do lot of work):
var httpRequest = new Mock<HttpRequestBase>();
var stream = new MemoryStream(Encoding.Default.GetBytes("Hello world"));
httpRequest.Setup(r => r.InputStream).Returns(stream);
var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(c => c.Request).Returns(httpRequest.Object);
var controller = new HomeController();
var routeData = new RouteData();
controller.ControllerContext = // set mocked context
new ControllerContext(httpContext.Object, routeData, controller);
var result = (JsonResult)controller.GetZZ();
Assert.That(result.Data, Is.EqualTo(42)); // your assertions here
Another option - hiding this environment-related stuff under some abstraction, which can be easily mocked (of course, you should use better names):
public interface IFoo
{
string Bar();
}
This is a implementation, which uses current context request to get input data:
public class Foo : IFoo
{
public string Bar()
{
using (var str = new StreamReader(HttpContext.Current.Request.InputStream))
{
string inputData = str.ReadToEnd();
return inputData;
}
}
}
Make controller depend on this abstraction:
public class HomeController : Controller
{
private readonly IFoo _foo;
public HomeController(IFoo foo) // inject dependency
{
_foo = foo;
}
public ActionResult GetZZ()
{
ApplyResponseHeaders();
var result = new JsonResult();
MyFunction(_foo.Bar(), result); // use dependency
return result;
}
}
Now you can mock it without any problems:
var foo = new Mock<IFoo>();
foo.Setup(f => f.Bar()).Returns("Hello, TDD");
var controller = new HomeController(foo.Object);
var result = (JsonResult)controller.GetZZ();
Assert.That(result.Data, Is.EqualTo(42));
One easy way is to make the method public. If you can't (or don't want to), you could make the method protected instead of private, then subclass your controller in your test assembly and test it through the derived type.
Something like so:
public class TesterController : YourController
{
public new ActionResult MyFunction(string inputData, MediaJsonResult result)
{
return base.MyFunction(inputData, result);
}
}
You can mark the methods as internal instead of private and then add a InternalsVisibleTo("Path.To.Test.Project") in AssemblyInfo.cs of your controllers.
Not 100% sure agree about NEVER EVER test a private method in your code. Like most things, sometimes it makes sense and being pragmatic is often better then being dogmatic.

Categories

Resources