When testing a specific API I had to check if the user was redirected to the correct location. However although I thought that I had written my api correctly, the response code didn’t match.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ApiController] | |
[Route("job")] | |
public class JobController : ControllerBase | |
{ | |
[HttpGet("{jobId}")] | |
public IActionResult Job(Guid jobId) | |
{ | |
//Return OK if job is still running/scheduled | |
var job=_jobRepository.Get(jobId); | |
if (job == null) | |
return NotFound(jobId); | |
if (job.Status == Status.Pending) | |
return Ok(); | |
//Return 500 if job failed | |
if (job.Status == Status.Failed) | |
{ | |
return StatusCode(500); | |
} | |
//Return 302 when job is finished | |
if (job.Status == Status.Succeeded) | |
{ | |
return RedirectToAction(nameof(JobOutPut), new { jobId }); | |
} | |
return NotFound(jobId); | |
} | |
} |
Here is the related test code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal static class HttpClientExtensions | |
{ | |
internal static async Task PollForResults(this HttpClient httpClient,Uri requestUri, Func<HttpResponseMessage, Task> successAction, Func<HttpResponseMessage, Task> failureAction = null) | |
{ | |
int numberOfAttempts = 3; | |
int attemptCount = 0; | |
//Keep querying the URI until we get a 302 with a result | |
while (attemptCount< numberOfAttempts) | |
{ | |
var pollingResponse = await httpClient.GetAsync(requestUri); | |
if (pollingResponse.StatusCode == System.Net.HttpStatusCode.OK) | |
{ | |
attemptCount++; | |
await Task.Delay(1000); | |
} | |
if (pollingResponse.StatusCode == System.Net.HttpStatusCode.InternalServerError) | |
{ | |
if (pollingResponse.Content.Headers.ContentType.Equals(new MediaTypeHeaderValue("application/zip"))) | |
{ | |
if (failureAction == null) | |
return; | |
await failureAction(pollingResponse); | |
} | |
break; | |
} | |
if (pollingResponse.StatusCode == System.Net.HttpStatusCode.Redirect) | |
{ | |
// Response should contain a location header with job output URI | |
var jobOutputLocation = pollingResponse.Headers.Location; | |
var outputResponse = await httpClient.GetAsync(jobOutputLocation); | |
if (successAction == null) | |
return; | |
await successAction(outputResponse); | |
break; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class JobApiTests : IClassFixture<SelfHostedApi> | |
{ | |
public HttpClient Sut { get; } | |
public GenerationApiTests(SelfHostedApi fixture) | |
{ | |
Sut = fixture.CreateClient(); | |
} | |
[Fact] | |
public async Task API_Enables_Bulk_Processing_Files_On_URL_Path() | |
{ | |
//Arrange | |
var templateFile = new Uri("http://localhost/ExampleTemplate.docx"); | |
var xmlFile = "ExampleXML.xml"; | |
var targetFile = $"ExampleDocument-{Guid.NewGuid()}.zip"; | |
var form = new MultipartFormBuilder() | |
.AddFile(xmlFile, "xmlFiles", "File1.xml") | |
.AddFile(xmlFile, "xmlFiles", "File2.xml") | |
.AddFile(xmlFile, "xmlFiles", "File3.xml") | |
.Build(); | |
//Act | |
var response = await Sut.PostAsync($"/job/bulk/uri?template={templateFile}", form); | |
response.EnsureSuccessStatusCode(); | |
//Response should contain a location header with job id | |
var jobLocation = response.Headers.Location; | |
await Sut.PollForResults(jobLocation, successAction: response => response.WriteToFile(targetFile,checkStatusCode:false)); | |
//Assert | |
Assert.True(File.Exists(targetFile)); | |
Assert.True(new FileInfo(targetFile).Length > 0); | |
} | |
} |
Do you spot my mistake? Let’s have a look at the documentation:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Summary: | |
// Creates an instance of System.Net.Http.HttpClient that automatically follows | |
// redirects and handles cookies. | |
// | |
// Returns: | |
// The System.Net.Http.HttpClient. | |
public HttpClient CreateClient() | |
{ | |
return CreateClient(ClientOptions); | |
} |
The default HttpClient will automatically handle redirects what makes it impossible to check for the 302 status code
To change this behavior, you need to create your own HttpHandler and use it when building up your HttpClient instance:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class JobnApiTests : IClassFixture<SelfHostedApi> | |
{ | |
public HttpClient Sut { get; } | |
public GenerationApiTests(SelfHostedApi fixture) | |
{ | |
var handler = new HttpHandler(); | |
Sut = fixture.CreateDefaultClient(handler); | |
} | |
internal class HttpHandler : DelegatingHandler | |
{ | |
public HttpHandler() | |
{ | |
//We override the default handler to avoid automatic redirection | |
//This makes it hard to detect the job completion in our tests | |
this.InnerHandler = new HttpClientHandler() | |
{ | |
AllowAutoRedirect = false | |
}; | |
} | |
} | |
} |