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.
Related
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);
I've tried many different approaches for the past couple of hours, but my method call is hanging up the thread.
Here is my Web API code, which works fine when making AJAX call from the MVC client, but I'm trying to test calling from the server:
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Below is my MVC controller code and model code:
public async Task<ActionResult> TestApi()
{
try
{
var result = await VoipModels.GetValues();
return MVCUtils.JsonContent(result);
}
catch (Exception ex)
{
return MVCUtils.HandleError(ex);
}
}
...
public static async Task<string[]> GetValues()
{
string[] result = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44305/api/");
//THIS IS THE LINE THAT HANGS UP - I'VE TRIED MANY VARIATIONS
var response = await client.GetAsync("values", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsAsync<string[]>();
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
return result;
}
I've used this format successfully when calling a separate, 3rd party API. I've run out of examples to try from my couple of hours of Googling.
What am I doing wrong here?
Check your port number. Based on your code, you have "hard coded" the port "http://localhost:44305/api/" which may likely be incorrect, you should convert that to grab it from the host
configuration instead.
Check your local machine's firewall. Make sure that your local machine's firewall is allowing connections to the port assigned.
Check your protocol. Ensure that you are using http or https appropriately in your request URL.
As a special note, there are very rare cases / exception cases that you would want to have a web API server call itself. Doing so, is rather inefficient design as it will consume resources for no gain (such a generating request and response).
I am currently using a web service, which offers 2 endpoints, as backups for fall over. I need to test all 2 endpoints before my code completely fails and then will need to log the exception. My thoughts were to be to return the status code of the HTML response using this:
Function1:
public string ValidateHttpRequest(string endpointUrl)
{
try
{
var url = endpointUrl;
HttpClient httpClient = new HttpClient();
var reponse = httpClient.GetAsync(endpointUrl);
return reponse.Result.StatusCode.ToString();
}
catch (Exception ex)
{
Log.Log("exception thrown in ValidateHttpRequest()! " + ex.ToString());
Log.Log(ex);
return null;
}
}
This is called from another function, say function2().
Function 2:
private bool function2()
{
//Specify the binding to be used for the client.
BasicHttpsBinding binding = new BasicHttpsBinding();
var epA = "https://www1.endpoint1.com/endpointService.asmx";
var epB = "https://www2.endpoint1.com/endpointService.asmx";
if (ValidateHttpRequest(epA)== "OK")
{
EndpointAddress address = new EndpointAddress("https://www1.enpoint1.com/endpointService.asmx");
_Client = new WebService.SoapClient(binding, address);
return true;
}
else if ((ValidateHttpRequest(epB))== "OK")
{
EndpointAddress address2 = new EndpointAddress(("https://www2.enpoint2.com/endpointService.asmx"));
else
{
// Now Log error here completely, and only fail here if both above checks return anything apart from 200 status code
LogException(“Only log exception if all endpoints fail”);
return false;
}
}
This is all well and good, however I need this to not fail on the first call, as I will need to check if the other endpoint is valid/active. The issue is that if the response is null, the exception is handled and I will not check the rest of my endpoints, how can I correctly ensure my code is safe with i.e. exceptions are handled correctly, but continuing my code to check all endpoints before completely failing and halting execution. it should fail if i receive any other response apart from 200 OK I have researched about how to check the HTTP response and all that I can come up with is this but it doesn’t completely suit my needs .If anyone could point me in the right direction or help with a solution I would be very grateful.
Thanks in advance
I am trying to send json to a web API using HttpClient.PostAsync. It works from a console application but not from my CRM plugin. Doing some research I noted that it is probably something to do with the context the plugin runs in and threading. Anyway here is my calling code:
public async void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target"))
{
if (context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "new_product")
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
try
{
if (entity.Contains("new_begindate") && entity.Contains("new_expirationdate"))
{
await OnlineSignUp(entity, service);
}
}
catch (InvalidPluginExecutionException)
{
throw;
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(OperationStatus.Failed, "Error signing up: " + e.Message);
}
}
}
}
}
And here is the relevant code for sending the json:
private async Task<HttpResponseMessage> OnlineSignUp(Entity license, IOrganizationService service)
{
...
var json = JsonConvert.Serialize(invitation);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "token=7d20f3f09ef24067ae64f4323bc95163");
Uri uri = new Uri("http://signup.com/api/v1/user_invitations");
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
int n = 1;
return response;
}
}
The exception is thrown with a message "Thread was being aborted". Can anyone tell me what I am doing wrong?
I would guess this is going to fail somewhat randomly based on use of async/await. I wouldn't think CRM really supports plugins returning control before they are complete. When it fails, it looks like the thread was in the process of being cleaned up behind the scenes.
CRM is already handling multi-threading of plugins and supports registering plugin steps as asynchronous if they are long running (or don't need to be run in the synchronous pipeline). It would make more sense to use a synchronous HTTP call here like:
var response = httpClient.PostAsync(uri, content).Result;
EDIT: To illustrate, this is an overly-trivialized example of what is most likely happening when CRM goes to kickoff your plugin and you're using async/await (from LinqPad).
static async void CrmInternalFunctionThatKicksOffPlugins()
{
var plugin = new YourPlugin();
//NOTE Crm is not going to "await" your plugin execute method here
plugin.Execute();
"CRM is Done".Dump();
}
public class YourPlugin
{
public async void Execute()
{
await OnlineSignUp();
}
private async Task<HttpResponseMessage> OnlineSignUp()
{
var httpClient = new HttpClient();
var r = await httpClient.PostAsync("http://www.example.com", null);
"My Async Finished".Dump();
return r;
}
}
Which will print:
CRM is Done My Async Finished
looks like you are using Json.NET, when you use external assemblies there are some things to take care of (merging) and not always the result works.
Try to serialize using DataContractJsonSerializer
example: http://www.crmanswers.net/2015/02/json-and-crm-sandbox-plugins.html
I wrote a unit test for exception. But looks like it is not working correctly. It is always saying '404 Not Found' Status. that means url request not found. If I paste same url on browser it HttpResponse.StatusCode says BAD REQUEST.
I don't understand why it is not working for Unit test.
[TestMethod()]
public void GetTechDisciplinesTestException()
{
var config = new HttpSelfHostConfiguration("http://localhost:51546/");
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
using (var server = new HttpSelfHostServer(config))
using (var client = new HttpClient())
{
server.OpenAsync().Wait();
using (var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:51546/api/techdisciplines/''"))
using (var response = client.SendAsync(request).Result)
{
//Here Response Status Code says 'Not Found',
//Suppose to be 'Bad Request`
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
}
server.CloseAsync().Wait();
};
}
I tried with HttpSelfHostServer which works fine and it uses IISExpress.
[TestMethod()]
public void GetTechDisciplinesTestException()
{
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:51546/api/techdisciplines/''"))
using (var response = client.SendAsync(request).Result)
{
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
}
};
}
So I dont know HttpSelfHostServer is not wkring in the code? How to force HttpSelfHostServer to use IISExpress? How to deal this?
Leaving aside why your specific method isn't working, could I suggest that you don't bother testing that particular behaviour via an HTTPRequest - just test directly against the controller class:
[TestMethod]
[ExpectedException(typeof(HttpResponseException))]
public void Controller_Throws()
{
try{
//setup and inject any dependencies here, using Mocks, etc
var sut = new TestController();
//pass any required Action parameters here...
sut.GetSomething();
}
catch(HttpResponseException ex)
{
Assert.AreEqual(ex.Response.StatusCode,
HttpStatusCode.BadRequest,
"Wrong response type");
throw;
}
}
Since this way you are truly "unit testing" that behaviour on the controller, and avoiding any indirect tests
For example if your controller goes off and tries to hit a database before you throw your HttpResponseException, then you're not really testing the controller in isolation -- because if you did get an exception back you'd not be 100% certain what threw it.
By testing directly you can inject for e.g. Mock dependencies that will do nothing other than what you tell them to do.