How to unit test webapi controller sends back error - c#

I have the following controller method:
public IHttpActionResult GetAttractions([FromUri] SearchAttractionRequest request)
{
try
{
var requestParameterType = request.RequestType();
var searchResultModel = _attractionService.GetAttractions(request);
return TransformListToResult(searchResultModel.Results, request.PerPage, searchResultModel.TotalItemCount, searchResultModel.GeneratedQuery);
}
catch (Exception ex)
{
// return Content(HttpStatusCode.BadRequest, "Any object");
// return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));
}
}
When making the call request.RequestType() if the parameter passed in is invalid an argument exception will be thrown and code will end up in catch block. I have a unit test that I am trying to write:
[Test]
public void Given_Mixed_Parameters_Expect_Error()
{
var invalidQuery = new SearchAttractionRequest
{
Category = Guid.NewGuid().ToString() + "|a catgeory|" + Guid.NewGuid().ToString(),
Genre = "",
Region = ""
};
var attractionServiceMock = Mock.Of<IAttractionService>();
var sut = new AttractionsApiController(attractionServiceMock);
Action act = () => sut.GetAttractions(invalidQuery);
act.ShouldThrow<HttpResponseException>();
}
Basically I want to test that when I send in invalid param I get back error response. So what do i need to send back from my api method and what do i need todo in my test to test for the error. At the moment I am getting error in my test becuase the expected error is argument exception, which is correct but i want to mimic what a client would get back.
Do I need todo something like this in my test:
as OkNegotiatedContentResult<SomeObject>;

You can't exactly write a test for the controller that will mimic what the client receives. That's because, by design, the controller is just a class with methods that return some result. Technically it doesn't determine what the client receives.
For example, if you call a method that throws an unhandled ArgumentException, other middleware is going to determine that the client should receive a 500 response. The controller isn't doing that.
Or, a controller might return a ViewResult. The client doesn't get a ViewResult - it gets HTML. The controller doesn't determine what that HTML will be.
When you're writing unit tests for the controller, think of it as just another class. You're just testing that it returns what it's supposed to.
If the controller throws an unhandled ArgumentException you can test for that.
If you handle the ArgumentException and return
Content(HttpStatusCode.BadRequest, "Any object");`
You could do this:
var result = (NegotiatedContentResult<string>)controller.GetResult();
Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode);

I believe you could use the following:
var attractionServiceMock = Mock.Of<IAttractionService>();
var sut = new AttractionsApiController(attractionServiceMock);
var result = sut.GetAttractions(invalidQuery) as BadRequestErrorMessageResult;
Assert.IsNotNull(result);

Related

How to Log Http Communication Errors in Angular

My .Net MVC application is attempting a an Http put call to update an existing record. I have noticed the controller put logic is not being triggered like other http communications.
I would like to include the HandleError logic found on Angular's Communication Page to write out the errors. When I include the error handler in my data service layer I get Argument of type 'Observable<never>' is not assignable to parameter of type '(err: any, caught: Observable<Record>) => ObservableInput<any>'
From what I see on I have the correct JSON object and API url. The controller can be reached if I copy the JSON object and URL into Postman.
Any insight provided on error handling and logging would be greatly appreciated.
Component logic:
updateRecord(record_id: number, newRecord: any): void
{
this.recordService.put<Record>(record_id, newRecord);
}
Data service logic:
put<Record>(record_id: number, record: Record): Observable<Record> {
var url = this.baseUrl + `api/record/${record_id}`;
let output = this.http.put<Record>(url, record, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
})
.pipe(
catchError(this.handleError('put<Record>', record))
);
return output;
}
Handle Error:
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
consol e.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong.
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// Return an observable with a user-facing error message.
return throwError(
'Something bad happened; please try again later.');
}
Controller logic:
[HttpPut("{id}")]
public async Task<ActionResult<Domain.Record>> Put(int id, [FromBody] Domain.Record record)
{
//Confirm the request record and ID record being update match
if (id != record.record_id)
return BadRequest();
//Modify the state
_context.Entry(record).State = EntityState.Modified;
//Update the records in DB.records, throw appropriate error if there is one.
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if (!RecordExists(record.record_id))
return NotFound();
else
throw;
}
//return 200 OK
return NoContent();
}
It looks like you were missing a subscribe somewhere, per the comments on the main question.
In RXJS an Observable that is not subscribed to will never execute. So, this:
updateRecord(record_id: number, newRecord: any): void
{
this.recordService.put<Record>(record_id, newRecord);
}
Should be turned into this:
updateRecord(record_id: number, newRecord: any): void
{
this.recordService.put<Record>(record_id, newRecord).subscribe((result) => {
// process results here
};
}
Without the subscribe, I do not believe you'll get any results from the HTTP call inside that recordService.put() from.

How to capture current Status code in ASP.NET CORE 3.1 Web API

I have written method that returns the result in the object. I need to capture current HttpStatusCode rather than I create my own and save in the following object before returning a result
[HttpPost]
public ActionResult CreateUser([FromBody]UsereDto user)
{
try
{
var data = UserService.CreateUser(user);
var result = new ResultDto
{
Ok = true,
Data = data,
Error = "No",
StatusCode = HttpStatusCode. ???????????????
};
return Ok(result);
}
catch(Exception exp)
{
var errorResult = new ResultDto
{
Ok = false,
Data = null,
Error = exp.ToString()
StatusCode = ????????????
};
return Ok(errorResult);
}
}
Can't capture existing http code
you can manually enter http codes yourself.
For Example:
Created Code=201
Error Code =400
BadRequest:301
NotFound:404
return StatusCode(200,object);
You can also return it this way.
I am not 100% sure what do you want to achieve or why do you even want that since HTTP Response already will contain an http status, so returning it additionally in a JSON response does not add more information than is already available.
But you can just hardcode the status since it is always 200 in your example, unless there is some error during serialization of the response, etc.
Right after your ??? line you do return an Ok() response that will return 200.
You don't need to create your own status code inside dto...
return Ok(errorResult); will return 200 with error and this is bad.
Instead, you should return BadRequestResult(errorResult) and ui will receive 500 result containing your DTO.
Also, instead of return Ok(result);, you can return OkResult(result) and this will return 200 to UI, also containing your DTO...
Also, you should extract Dto creation to it's own method in separate class, to follow DRY and single responsibility principles..

Web API 2.0 Exception Handling

I am using Web API 2.0 to create my own project.
My Api contain ability to Add , Get and book product.
I Want to handle exception but there is some issue I little bit confused.
I have a controller: ProdctController with action method AddProdct.
[Route("~/CKService.svc/author/add")]
[HttpPost]
public IHttpActionResult AddProduct(ProductRequest request)
{
ProductManager.AddProduct(request.ProductId, request.ProductName,
request.Price);
return Ok(new AddProductResponse()
{
Status = AddProductStatus.Success
};)
}
Now, if the product already exists, so the data access layer will throw DuplicateKeyException
I want to handle this excpetion and return response like this:
return Ok(new AddProductResponse()
{
Status = AddProductStatus.AlreadyExists
};)
Is this reasonable to return HTTP status code 200?
How can I do this without add catch DuplicateKeyException to my controller because i think this is not the right way.
Thanks
Here is one suggestion. (and I emphasis that it is just one of the many options you have available)
Refactor the manager to handle and return the status of adding the product and then have the action return the appropriate response code based on the status.
[Route("~/CKService.svc/author/add")]
[HttpPost]
public IHttpActionResult AddProduct(ProductRequest request) {
var status = ProductManager.AddProduct(request.ProductId, request.ProductName, request.Price);
var response = new AddProductResponse() {
Status = status
};
if(status == AddProductStatus.Success) {
return Ok(response);
}
return BadRequest(response);
}
Avoid return 200 OK for requests that have not completed as intended. That will mislead the client making the request.
HTTP 200 is not resonable - it indicates a successfull request. Use a 4xx (Error) Code instead. 409 (conflict) fits your situation of an already existing object.
See: HTTP response code when resource creation POST fails due to existing matching resource
To catch the exception is totally fine. An unhandled exception would result in a 500 error (internal server error) which is not meaningful by any means.
Instead you should catch the Exception and return a HTTP Error like this (Exception Handling ASP.NET) :
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.Conflict, message))

Mocking out the Data Accessor

I am trying to learn about using Moq with NUnit and IoC.
(I have my full project in BitBucket, but not sure how to share it...)
https://bitbucket.org/Cralis/skeleton/overview
I have a Logic method (Login) I am trying to test. It take a request object (Which has a username, password and IP Address). If the username and/or password are empty, the logic returns a failed status, and doesn't go to the data access layer.
So I am creating a unit test to test this.
(This my my first attempt with mocking...)
public void NotNull_Returns_True()
{
// Arrange
var request = new LoginRequest { IPAddress = "1.1.1.1", Username = "dummy", Password = "dummy" };
var response = new LoginResponse { Request = request, Success = true, Message = "", UserID = 1 };
// Setup the moc data accessor, as we don't want to gop to the concrete one.
var MockedDataAccess = new Mock<IDataAccess>();
// Set it's return value
MockedDataAccess.Setup(x => x.Login(request)).Returns(response);
// Instantiate the Logic class we're testing, using a Moc data accessor.
var logic = new BusinessLogic(MockedDataAccess.Object);
// Act
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
// Assert
Assert.AreEqual(true, result.Success);
}
This fails on the assert, as 'result' is NULL.
I'm probably doing a lot wrong. For example, I'm not sure why I need to setup the request and response objects at the top,but because all the examples I find are 'string' and 'int' inputs, it seems I can't use It.IsAny...
Could someone assist me understanding here? What am I doing wrong to get NULL as a result in the assert? I step through and the code executes as expected. But the result is null, because I never called the data accessor (It used the mock).
Edit:
Ah,
// Set it's return value
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response);
That resolved the issue. I'm not sure why, so if you can help me understand and refactor this so that it's as an experienced Moq/UnitTester would expect it to look, that would be very useful.
Even though your request object has the same property values that you're passing to var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });, they are different objects, and so the value that you're trying to return with MockedDataAccess.Setup(x => x.Login(request)).Returns(response); isn't getting returned.
Change
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
to
var result = logic.Login(request);
The reason it worked with MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response); is because now you're saying "when MockedDataAccess.Login is called with any value for its parameter, return response"
Regarding the second part of your question, the reason that you need to set up the request and response objects is that by default any method that you call on a mock object will return null. Your BusinessLogic.Login method, listed below, will return the value of dataAccess.Login(). Since dataAccess is a mock, the dataAccess.Login() method will return null unless you tell it otherwise.
public LoginResponse Login(LoginRequest request)
{
// Basic validation
if (request == null)
return new LoginResponse
{
Success = false,
Message = "Empty Request"
};
if (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))
return new LoginResponse
{
Success = false,
Message = "Username and/or password empty"
};
// This is returning null since dataAccess is a mock
return dataAccess.Login(request);
}
You said you think you're doing a lot wrong, but the way you have the test set up is pretty much what I do. The only thing I would change (in addition to fixing the Setup method as described above) is to use the UnitOfWork_StateUnderTest_ExpectedBehavior naming pattern for your test. For example Login_ValidLoginRequest_ShouldReturnValidLoginResponse()
The problem with this code
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" })
is that in the implementation of the Login method this is called
dataAccess.Login(request)
It means you have to setup the mock of DataAccess for method Login because mock does otherwise nothing. Mock if fake and needs to be setup so it works the way you need. In this case the answer of #Ben Rubin is absolutely correct.
When mock is setup like this
MockedDataAccess.Setup(x => x.Login(request)).Returns(response)
then it is necessary to call the method under test with exactly the same request object as the request which was used in setup of data access Login method, because otherwise mock will act as not setup. Here you basically saying 'when DataAccess Login is called with exactly this request, that response will be returned'.
But when mock is setup like this
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response)
then it works, because here Login is setup to any LoginRequest. So the mock will in this case return response no matter what request was used. HTH
Here are more information about Mock Matching Arguments

What should a controller integration test assert

Doing an integration test on a web api endpoint what should I put my focus on to assert?
My endpoint is also doing a call to a domain service.
Should I mock that service? With the current code that is not possible, because I would need to instantiate the controller to pass the mock service.
Am I interested in the service return value? actually not.
I am only interested wether the endpoint was succesfully triggered but then I should isolate the service call I guess.
Any advice is welcome :-)
TEST
[TestClass]
public class SchoolyearControllerTests
{
private TestServer _server;
[TestInitialize]
public void FixtureInit()
{
_server = TestServer.Create<Startup>();
}
[TestCleanup]
public void FixtureDispose()
{
_server.Dispose();
}
[TestMethod]
public void Get()
{
var response = _server.HttpClient.GetAsync(_server.BaseAddress + "/api/schoolyears").Result;
var result = response.Content.ReadAsAsync<IEnumerable<SchoolyearDTO>>().GetAwaiter().GetResult();
Assert.AreEqual(response.StatusCode, HttpStatusCode.OK);
}
}
Action to test
[HttpGet]
public async Task<IHttpActionResult> Get()
{
var schoolyears = await service.GetSchoolyears();
return Ok(schoolyears);
}
The trouble with doing an integration test on a web service is that it doesn't tell you very much about the problem - or even if there actually is one, and if there is, it doesn't tell you where the issue lies. It will either succeed or fail. So in that respect did you get a 200 response code or a 500 response code... but did it fail because:
The server was unreachable
The web service isn't started, or failed when trying to start
A firewall is blocking the network
A database record wasn't found
There's a database schema problem and entity framework didn't start
properly.
It could literally be anything - and result might be different on your dev machine than in production - so what does it really tell you about your application?
What makes for robust software is to test that your product is able to handle any of these situations correctly, gracefully and robustly.
I write my controller actions like this:
public HttpResponseMessage Get(int id)
{
try
{
var person = _personRepository.GetById(id);
var dto = Mapper.Map<PersonDto>(person);
HttpResponseMessage response = Request.CreateResponse<PersonDto>(HttpStatusCode.OK, dto);
return response;
}
catch (TextFileDataSourceException ex)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError);
return response;
}
catch (DataResourceNotFoundException ex)
{
HttpResponseMessage response = Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
return response;
}
catch (FormatException ex)
{
HttpResponseMessage response = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
return response;
}
catch (Exception ex)
{
HttpResponseMessage response = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
return response;
}
}
A try block gets the data, makes a dto and returns the data with a 200 code. There are several error conditions handled here, but none indicate a problem with my web service itself and some (404 error) don't even indicate an issue with the application - I EXPECT a NotFoundException and a 404 if my application can't find a record - if this happens my application WORKS in this scenario.
So if any of these error conditions occur, its not because there's a problem with the web service, and not necessarily a problem with the app. But I can test that my web service is returning the correct response for any of these expected conditions.
The tests for this controller action looks like this:
[Test]
public void CanGetPerson()
{
#region Arrange
var person = new Person
{
Id = 1,
FamilyName = "Rooney",
GivenName = "Wayne",
MiddleNames = "Mark",
DateOfBirth = new DateTime(1985, 10, 24),
DateOfDeath = null,
PlaceOfBirth = "Liverpool",
Height = 1.76m,
TwitterId = "#WayneRooney"
};
Mapper.CreateMap<Person, PersonDto>();
var mockPersonRepository = new Mock<IPersonRepository>();
mockPersonRepository.Setup(x => x.GetById(1)).Returns(person);
var controller = new PersonController(mockPersonRepository.Object);
controller.Request = new HttpRequestMessage(HttpMethod.Get, "1");
controller.Configuration = new HttpConfiguration(new HttpRouteCollection());
#endregion
#region act
HttpResponseMessage result = controller.Get(1);
#endregion
#region assert
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
#endregion
}
[Test]
public void CanHandlePersonNotExists()
{
#region Arrange
var mockPersonRepository = new Mock<IPersonRepository>();
mockPersonRepository.Setup(x => x.GetById(1)).Throws<DataResourceNotFoundException>();
var controller = new PersonController(mockPersonRepository.Object)
{
Request = new HttpRequestMessage(HttpMethod.Get, "1"),
Configuration = new HttpConfiguration(new HttpRouteCollection())
};
#endregion
#region Act
HttpResponseMessage result = controller.Get(1);
#endregion
#region Assert
Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode);
#endregion
}
[Test]
public void CanHandleServerError()
{
#region Arrange
var mockPersonRepository = new Mock<IPersonRepository>();
mockPersonRepository.Setup(x => x.GetById(1)).Throws<Exception>();
var controller = new PersonController(mockPersonRepository.Object);
controller.Request = new HttpRequestMessage(HttpMethod.Get, "1");
controller.Configuration = new HttpConfiguration(new HttpRouteCollection());
#endregion
#region Act
HttpResponseMessage result = controller.Get(1);
#endregion
#region Assert
Assert.AreEqual(HttpStatusCode.InternalServerError, result.StatusCode);
#endregion
}
Note that I'm introducing a mock respository and having my mock repository trigger the expected exceptions for 404 and server error and ensuring that the web service handles it correctly.
What this tells me is that my web service handling expected and exceptional situations as it should and returning the appropriate codes: 200/404/500.
Although some are error states and some are success states, none of these outcomes indicate a problem with my web service - it is behaving exactly as it should, and that's what I want to test.
An 'across the network' integration test on a web service tells you nothing about the robustness or correctness of your application - or even if its returning the correct data or response-code.
Don't try to re-test WebAPI... Microsoft wrote a massive suite of tests for it already - hundreds of test-fixture classes, thousands of test methods:
https://aspnetwebstack.codeplex.com/SourceControl/latest#test/System.Web.Http.Test/Controllers/ApiControllerTest.cs
Assume that WebAPI works as it should and doesn't require you to test it again. Focus on testing your application code and make sure success and error conditions are handled gracefully by your web service.
If you want to check your web service is connected and available on the network - open a browser and test it manually; there's no need to automate this; the result will vary between environments and according to external conditions.
Test each layer of your application the same way, mock out the layer above and test that the current layer handles every possible outcome from the layer above.
Your client application for your service should do the same thing: Mock the web service, and pretend that it gave a 404 - and check that it handles this as it should.

Categories