I have the following controller which retrieves data from an endpoint.
It can also sort data depending on whether or not type is set.
What would be the best approach to testing it?
public class UsersController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAllUsers(string type)
{
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://demo10102020.mockable.io");
var response = await client.GetAsync($"/people");
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
List<User> rawUsers = JsonConvert.DeserializeObject<User[]>(stringResult).ToList();
List<User> sortedUsers = rawUsers;
if(type == "first-name")
{
sortedUsers = rawUsers.OrderBy(o => o.FirstName).ToList();
}
else if(type == "score")
{
sortedUsers = rawUsers.OrderBy(o => o.Score).ToList();
}
return Ok(sortedUsers);
}
catch (HttpRequestException httpRequestException)
{
return BadRequest($"Error getting users: {httpRequestException.Message}");
}
}
}
}
This is my approach so far, I am not sure how to mock the API:
[TestClass]
public class TestPersonController
{
[TestMethod]
public void GetAllPersons_ShouldReturnAllProducts()
{
var testPersons = GetTestPersons();
var controller = new PersonController();
}
private List<Person> GetTestPersons()
{
var testPersons = new List<Person>();
testPersons.Add(new Person { FirstName = "dfdfdf", Surname = "dfdfdf", Score = 100 });
testPersons.Add(new Person { FirstName = "dfsdfsfasf", Surname = "safasfsdaf", Score = 200 });
testPersons.Add(new Person { FirstName = "asffas", Surname = "asdffasdf", Score = 200 });
return testPersons;
}
}
Not sure if its worth but i would go about, moving the api call to a separate factory class and the sorting. Controller to only get the final value from your factory. I may have an orchestrator factory to call the api invoking factory and do the sorting.
Then, the Unit test can be done for
api factory by extending HttpClient and create methods to return test/mock data.
orchestrator factory to expect the list of strings or input data and test the sorted data.
then comes the test for the controller
just as an opinion, I think your method does too many things
I would put in another method the call Http
For example :
[HttpGet]
public async Task <IActionResult> GetAllUsers (string type)
{
List <User> rawUsers = UserExternalService.GetAllUser()
if (type == "first-name")
{
return Ok (rawUsers.OrderBy (o => o.FirstName) .ToList ());
}
else if (type == "score")
{
return Ok (rawUsers.OrderBy (o => o.Score) .ToList ());
}
return Ok (rawUsers);
}
This method would take 3 unit test
Example:
public class The_Method_GetAllUsers
{
[Fact]
public async void Should_return_user_when_type_is_name
{
Assert.IsType<OkObjectResult>>(this.Sut.GetAllUser("name"));
}
[Fact]
public async void Should_return_user_when_type_is_score
{
Assert.IsType <OkObjectResult>>(this.Sut.GetAllUser("score"));
}
[Fact]
public async void Should_return_user_when_type_its_not_name_and_score
{
Assert.IsType <OkObjectResult>>(this.Sut.GetAllUser("surname"));
}
}
I think it is a cleaner solution, it would be missing the unit test of the service that we believe is only called http and with its try / catch
Mock HttpClient Mock HttpClient using Moq
Related
In the below code, I'm using Moq to test if my Post method is returning CreatedAtRoute StatusCode on successfully addind a book but the test case is failing in Act part when it calls the service and shows the following error:
Moq.MockException: 'IEventRepository.AddEvent(TEMS.Business.Entities.Event) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.'
Test Setup and Tear Down:
private Mock<IBookRepository> mockBookRepository;
public override void TestSetup()
{
mockBookRepository = this.CreateAndInjectMock<IBookRepository>();
Target = new BooksController(mockBookRepository.Object);
}
public override void TestTearDown()
{
mockBookRepository.VerifyAll();
}
TestCase:
[Fact]
public void AddBook_ReturnCreatedAtRoute()
{
// to prevent autofixture throwing ThrowingRecursionBehavior
var fixture = new Fixture();
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior(1));
var books = fixture.Create<Book>();
this.mockBookRepository.Setup(s => s.AddBook(books)).Returns(1);
// Act
BookInputModel booksViewModel = convertDbModelToInputModel(books);
var actual = Target.Post(booksViewModel);
// Assert
var actionResult = Assert.IsType<ObjectResult>(actual);
Assert.Equal((int)HttpStatusCode.Created, actionResult.StatusCode);
this.mockBookRepository.Verify(v => v.AddBook(books), Times.Once);
}
Post Method:
public ActionResult Post(BookInputModel request)
{
try
{
Book addBooks = convertDbModelToInputViewModel(request);
var result = _service.AddBook(addBooks);
if (result == 0) return BadRequest();
return CreatedAtRoute("GetBook", new { id = addBooks.Id }, request);
}
catch (Exception ex)
{
return StatusCode(500, ex.Message);
}
}
Repository:
public int AddBook(Book request)
{
Book newBook = new Book()
{
Name = request.Name,
Author = request.Author,
Publication = request.Publication,
TotalPages = request.TotalPages,
};
if (newBook == null) return 0;
else
{
_context.Book.Add(newBook);
_context.SaveChanges();
return 1;
}
}
Took me a while to realise this little error that was causing the problem - the problem was happening in Act part for the very valid reason as I'm doing setup using DbModel and in Act I'm passing ViewModel(as the Post method in controller requires it) which raises the conflict. Now as far as I'm aware of, the only way to resolve it by using the same model in Post method as of Repository.
I'm in the process of building an ASP.NET Core WebAPI and I'm attempting to write unit tests for the controllers. Most examples I've found are from the older WebAPI/WebAPI2 platforms and don't seem to correlate with the new Core controllers.
My controller methods are returning IActionResults. However, the IActionResult object only has a ExecuteResultAsync() method which requires a controller context. I'm instantiating the controller manually, so the controller context in this instance is null, which causes an exception when calling ExecuteResultAsync. Essentially this is leading me down a very hacky path to get these unit tests to successfully complete and is very messy. I'm left wondering that there must be a more simple/correct way of testing API controllers.
Also, my controllers are NOT using async/await if that makes a difference.
Simple example of what I'm trying to achieve:
Controller method:
[HttpGet(Name = "GetOrdersRoute")]
public IActionResult GetOrders([FromQuery]int page = 0)
{
try
{
var query = _repository.GetAll().ToList();
int totalCount = query.Count;
int totalPages = (int)Math.Ceiling((double)totalCount / pageSize) - 1;
var orders = query.Skip(pageSize * page).Take(pageSize);
return Ok(new
{
TotalCount = totalCount,
TotalPages = totalPages,
Orders = orders
});
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
Unit test:
[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk()
{
// arrange
var controller = new OrdersController(new MockRepository());
// act
IActionResult result = controller.GetOrders();
// assert
Assert.Equal(HttpStatusCode.OK, ????);
}
Assuming something like the
public IActionResult GetOrders() {
var orders = repository.All();
return Ok(orders);
}
the controller in this case is returning an OkObjectResult class.
Cast the result to the type of what you are returning in the method and perform your assert on that
[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk() {
// arrange
var controller = new OrdersController(new MockRepository());
// act
var result = controller.GetOrders();
var okResult = result as OkObjectResult;
// assert
Assert.IsNotNull(okResult);
Assert.AreEqual(200, okResult.StatusCode);
}
You can also do cool things like:
var result = await controller.GetOrders();//
var okResult = result as ObjectResult;
// assert
Assert.NotNull(okResult);
Assert.True(okResult is OkObjectResult);
Assert.IsType<TheTypeYouAreExpecting>(okResult.Value);
Assert.Equal(StatusCodes.Status200OK, okResult.StatusCode);
Thanks
Other answers adviced to cast to ObjectResult, but its work only if you return OkObjectResult \ NotFoundObjectResult \ etc. But server could return NotFound\ OkResult which derived from StatusCodeResult.
For example:
public class SampleController : ControllerBase
{
public async Task<IActionResult> FooAsync(int? id)
{
if (id == 0)
{
// returned "NotFoundResult" base type "StatusCodeResult"
return NotFound();
}
if (id == 1)
{
// returned "StatusCodeResult" base type "StatusCodeResult"
return StatusCode(StatusCodes.Status415UnsupportedMediaType);
}
// returned "OkObjectResult" base type "ObjectResult"
return new OkObjectResult("some message");
}
}
I looked at the implementation of all these methods and found that they are all inherited from the IStatusCodeActionResult interface. It seems like this is the most base type that contains StatusCode:
private SampleController _sampleController = new SampleController();
[Theory]
[InlineData(0, StatusCodes.Status404NotFound)]
[InlineData(1, StatusCodes.Status415UnsupportedMediaType)]
[InlineData(2, StatusCodes.Status200OK)]
public async Task Foo_ResponseTest(int id, int expectedCode)
{
var actionResult = await _sampleController.FooAsync(id);
var statusCodeResult = (IStatusCodeActionResult)actionResult;
Assert.Equal(expectedCode, statusCodeResult.StatusCode);
}
A good way to do that is like this:
[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk() {
// arrange
var controller = new OrdersController(new MockRepository());
// act
var result = controller.GetOrders();
// assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.IsNotNull(okResult);
Assert.AreEqual(200, okResult.StatusCode);
}
You also can use ActionResult class as a controller result (assuming you have type Orders).
In that case you can use something like this:
[ProducesResponseType(typeof(Orders), StatusCodes.Status200OK)]
public ActionResult<Orders> GetOrders()
{
return service.GetOrders();
}
and now in unit tests you have:
Assert.IsInstanceOf<Orders>(result.Value);
Besides, this is the recommendation of Microsoft - https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-2.2#actionresultt-type
Unfortunately, I don't know why using Ok method
return Ok(service.GetOrders());
doesn't map it properly.
public async Task CallRxData_ReturnsHttpNotFound_ForInvalidJobNum_ReturnsStoredRxOrder()
{
var scanInController = new ScanInController(_logger, _scanInService);
var okResult = await scanInController.CallRxData(rxOrderRequest);
var notFoundResult = await scanInController.CallRxData(invalidRxOrderRequest);
var okResultWithScanInCheckFalse = await scanInController.CallRxData(rxOrderRequest);
var okResultWithEmptyAelAntiFakeDatas = await scanInController.CallRxData(rxOrderRequest);
// Assert
Assert.That(okResult, Is.TypeOf<OkObjectResult>());
Assert.That(notFoundResult, Is.TypeOf<NotFoundObjectResult>());
Assert.IsFalse(((okResultWithScanInCheckFalse as ObjectResult).Value as RxOrder).IsSecurity);`enter code here`
}
I'm new to testing with Moq, so I'm confused a bit regarding ReturnsAsync.
Here is my test method, where I'm expecting ReturnsAsync should receive type that will be returned by method defined in Setup. Am I right?
But seemed ReturnsAsync should have the other signature - it's looking for Func delegate.
[TestClass]
public class TourObjectControllerTest
{
[TestMethod]
public async Task GetTourObjects()
{
var mockService = new Mock<ITourObjectService>(MockBehavior.Default);
mockService.Setup(x => x.GetAll()).ReturnsAsync(new Task<IEnumerable<TourObjectDTO>>);
var controller = new TourObjectController(mockService.Object);
var result = await controller.Get();
Assert.IsNotNull(result);
Assert.AreSame(typeof(TourObjectViewModel), result);
}
}
Method under test:
public async Task<IEnumerable<TourObjectViewModel>> Get()
{
IEnumerable<TourObjectViewModel> viewmodel = null;
try
{
var tourobjects = await _tos.GetAll();
viewmodel = Mapper.Map<IEnumerable<TourObjectViewModel>>(tourobjects);
}
catch (Exception ex)
{
Log.ErrorFormat("Method:{0} <br/> Error: {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
}
return viewmodel;
}
Pass an actual result.
Assuming ITourObjectService.GetAll() returns Task<IEnumerable<TourObjectDTO>>
eg
[TestClass]
public class TourObjectControllerTest
{
[TestMethod]
public async Task GetTourObjects()
{
var fakeData = new List<TourObjectDTO>() {
new TourObjectDTO { ... }
};
var mockService = new Mock<ITourObjectService>(MockBehavior.Default);
mockService.Setup(x => x.GetAll()).ReturnsAsync(fakeData);
var controller = new TourObjectController(mockService.Object);
var result = await controller.Get();
Assert.IsNotNull(result);
Assert.IsTry(typeof(IEnumerable<TourObjectViewModel>).IsAssignableFrom(result.GetType());
}
}
ReturnsAsync() should take a return value as the paramter instead of Task<TReurnValue>. For example, next 2 lines of code are equal (by behavior):
mockService.Setup(x => x.GetAll()).ReturnsAsync(new List<TourObjectDTO>());
mockService.Setup(x => x.GetAll()).Returns(Task.FromResult(new List<TourObjectDTO>()));
You need to replace new List<TourObjectDTO>() with your test data which you want to retrieve in the test from the mock. For example, you can create just few test values:
var testData = new [] { new TourObjectDTO(1, "test1"), new TourObjectDTO(2, "test2") };
mockService.Setup(x => x.GetAll()).ReturnsAsync(testData);
or create fake data generator if needed.
Note that ReturnsAsync() is only available for methods that return a Task<T>. For methods that return only a Task, .Returns(Task.FromResult(default(object))) can be used.
I want to unit test my web API controller. I have a problem with one of my action method (POST) which is need value from Request object, to get the controller name.
I'm using NSubtitute, FluentAssertions to support my unit test
This is my controller code looks like:
public class ReceiptsController : BaseController
{
public ReceiptsController(IRepository<ReceiptIndex> repository) : base(repository) { }
..... Other action code
[HttpPost]
public IHttpActionResult PostReceipt(string accountId, [FromBody] ReceiptContent data, string userId = "", string deviceId = "", string deviceName = "")
{
if (data.date <= 0)
{
return BadRequest("ErrCode: Save Receipt, no date provided");
}
var commonField = new CommonField()
{
AccountId = accountId,
DeviceId = deviceId,
DeviceName = deviceName,
UserId = userId
};
return PostItem(repository, commonField, data);
}
}
And the base class for my controller :
public abstract class BaseController : ApiController
{
protected IRepository<IDatabaseTable> repository;
protected BaseController(IRepository<IDatabaseTable> repository)
{
this.repository = repository;
}
protected virtual IHttpActionResult PostItem(IRepository<IDatabaseTable> repo, CommonField field, IContent data)
{
// How can I mock Request object on this code part ???
string controllerName = Request.GetRouteData().Values["controller"].ToString();
var result = repository.CreateItem(field, data);
if (result.Error)
{
return InternalServerError();
}
string createdResource = string.Format("{0}api/accounts/{1}/{2}/{3}", GlobalConfiguration.Configuration.VirtualPathRoot, field.AccountId,controllerName, result.Data);
var createdData = repository.GetItem(field.AccountId, result.Data);
if (createdData.Error)
{
return InternalServerError();
}
return Created(createdResource, createdData.Data);
}
}
And this is my unit test for success create scenario:
[Test]
public void PostClient_CreateClient_ReturnNewClient()
{
// Arrange
var contentData = TestData.Client.ClientContentData("TestBillingName_1");
var newClientId = 456;
var expectedData = TestData.Client.ClientData(newClientId);
clientsRepository.CreateItem(Arg.Any<CommonField>(), contentData)
.Returns(new Result<long>(newClientId)
{
Message = ""
});
clientsRepository.GetItem(accountId, newClientId)
.Returns(new Result<ContactIndex>(expectedData));
// Act
var result = _baseController.PostClient(accountId, contentData, userId);
// Asserts
result.Should().BeOfType<CreatedNegotiatedContentResult<ContactIndex>>()
.Which.Content.ShouldBeEquivalentTo(expectedData);
}
I don't know if there is any way to extract Request object from the controller, or maybe is there any way to mock it on the unit test?
Right now this code Request.GetRouteData() return null on the unit test.
you can make an interface for getting Request Data(pass Request object to it). Implement that interface and use as dependency in your Controller. Then you can easily mock this interface implementation in your unit tests.
I've finally find a way to solve this. So basically I have to create some configuration related stuff to make my unit test works.
I create a helpers class for this
public static class Helpers
{
public static void SetupControllerForTests(ApiController controller)
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/products");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
}
}
Then passing my test controller on my test setup
[SetUp]
public void SetUp()
{
clientsRepository = Substitute.For<IRepository<ContactIndex>>();
_baseController = new ClientsController(clientsRepository);
Helpers.SetupControllerForTests(_baseController);
}
I don't know if this is a best way to do it, but I prefer this way instead of create a new interface and inject it to my controller.
I have problems with testing post method.
In that method AntiForgeryToken is checked and I don't know to how to mock it.
Beside that everythings works.
Here my method I want to test:
[HttpPost]
//[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Id,Name,Manufacturer,CatalogNo")] Device device)
{
ValidateRequestHeader(Request);
if (String.IsNullOrWhiteSpace(device.Name)||String.IsNullOrWhiteSpace(device.Manufacturer)||String.IsNullOrWhiteSpace(device.CatalogNo))
{
ModelState.AddModelError("", "Niepoprawne dane");
return PartialView(device);
}
unitOfWork.deviceRepository.Insert(device);
unitOfWork.Save();
return Json(new { ok = true, newurl = Url.Action("Index") });
}
I have already mocked:
private IUnitOfWork fakeRepo;
DeviceController DC;
[TestInitialize]
public void Initialize()
{
Mock<IUnitOfWork> mock = new Mock<IUnitOfWork>();
mock.Setup(m => m.deviceRepository.Get(
It.IsAny<List<Expression<Func<Device, bool>>>>(),
It.IsAny<Func<IQueryable<Device>, IOrderedQueryable<Device>>>(),
null))
.Returns(new[] { new Device { Id = 1, Manufacturer = "a", Name = "b", CatalogNo = "x" } });
mock.Setup(m => m.deviceRepository.Get()).Returns(new[] { new Device { Id = 1, Manufacturer = "a", Name = "b", CatalogNo = "z" } });
fakeRepo = mock.Object;
DC = new DeviceController(fakeRepo);
}
But I'dont know how to method testing AntiforgeryToken
here code:
public void ValidateRequestHeader(HttpRequestBase request)
{
string cookieToken = "";
string formToken = "";
if (request.Headers["RequestVerificationToken"] != null)
{
string[] tokens = request.Headers["RequestVerificationToken"].Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
I can't comment out this call testing purpose but this solution seems little ugly.
Can You suggest any edits?
It looks like you need to mock HttpRequestBase and AntiForgery.
How do you mock them?
You wrap them in your own interfaces exposing the behaviour you need or may need in the very near future. In your production code you provide the real .NET implementations, in your tests, your mocks.
MSDN says AntiForgery.Validate
Validates that input data from an HTML form field comes from the user
who submitted the data.
the signature that takes two string arguments returns void.
Your mock IAntiForgeryValidator would have a Validate(string, string) method that also returns void.
public interface IAntiForgeryValidator
{
void Validate(string cookieToken, string formToken);
}
public class AntiForgeryValidator : IAntiForgeryValidator
{
public void Validate(string cookieToken, string formToken)
{
AntiForgery.Validate(cookieToken, formToken);
}
}
You can use a call back for void methods and verify that they were called the correct number of times:
antiForgeryMock.Setup(m => m.Validate(
It.IsAny<string>(),
It.IsAny<string>()))
.Callback((string cookieToken, string formToken) =>
{
// call back
});
antiForgeryMock.Verify(m => m.Validate(
It.IsAny<string>(),
It.IsAny<string>()), Times.Once());
You have another option (cheating) and that is to stub your call to ValidateRequestHeader() in Create(). This will enable you to test the rest of your code but is not reccomended because the real ValidateRequestHeader() could cause trouble in your production code if you leave the method un-tested.