Simulate HttpRequestException for unit testing - c#

I have an application that downloads massive amounts of pdfs from the web. From time to time, I get a HttpRequestException, contaning the message: Error while copying content to a stream.
So, I am trying to unit test my code to handle this situation. My current code for downloading is:
var request = await httpClient.GetAsync(url);
// This is where the exception would be thrown
await request.Content.ReadAsByteArrayAsync());
Now I am trying to simulate a HttpRequestException, so that I can unit test the code above, but I dont know how to do it. Can anyone please help?
Thanks in advance!

The key here is creating an HttpContent that throws an exception:
public class ExceptionThrowingContent : HttpContent
{
private readonly Exception exception;
public ExceptionThrowingContent(Exception exception)
{
this.exception = exception;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return Task.FromException(exception);
}
protected override bool TryComputeLength(out long length)
{
length = 0L;
return false;
}
}
Once you have that, you can use something like my own mockhttp to mock the request:
var handler = new MockHttpMessageHandler();
handler.When("http://tempuri.org/url")
.Respond(new ExceptionThrowingContent(ex));
var mockClient = new HttpClient(handler);
// pass mockHandler to your component
Now, if your component passes in HttpCompletionOption.ResponseHeadersRead when it makes the request, the exception will be thrown at await Content.ReadAsByteArrayAsync(). If not, HttpClient will attempt to buffer the response so the exception will be thrown at await HttpClient.GetAsync().
If the response is being buffered, it's realistically "impossible" for an exception to be thrown at ReadAsByteArrayAsync so there's no point in attempting to simulate it. ("impossible" outside an OutOfMemoryException)

With MockHttp it is easy setup HttpClient tests in a fluent manner you can return a customized HttpContent that throws a HttpRequestException like in this example.
[TestMethod]
[ExpectedException(typeof(HttpRequestException))]
public async Task Test()
{
var content = new ContentWithException();
var mockHttp = new MockHttpMessageHandler();
mockHttp.Expect("http://localhost/mypdfDownload").Respond(content);
var client = new HttpClient(mockHttp);
var response = await client.GetAsync("http://localhost/mypdfDownload");
await response.Content.ReadAsByteArrayAsync();
}
private class ContentWithException : HttpContent
{
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
throw new HttpRequestException();
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
}

Related

Adding a timeout in a specific part of code in c# programming language

Hello I have a wrapservice which calls sub services in the project. I am trying to add timeout value as a second but there is too many variables so it makes thing hard to implement. I am adding my code snippet and open to ideas thank you
public Stream Posnet(Stream request)
{
if (!Globals.IsConsumerAuthorizedForWasService)
{
throw new FaultException("consumer is not authorized", new FaultCode("3001"));
}
var streamReader = new StreamReader(request);
string streamString = streamReader.ReadToEnd();
streamReader.Close();
PosnetResponse resp = _merchantLegacyAPIWrapper.WrapService(streamString); // I want to add timeout here where I call WrapService;
// I want to add timeout here where I call WrapService
string responseXml = SerializeResponse(resp);
return GenerateStreamFromString(responseXml);
}
and I tried this code snippet below to implement but not succeed:
using System.Threading.Tasks;
var task = Task.Run(() => SomeMethod(input));
if (task.Wait(TimeSpan.FromSeconds(10)))
return task.Result;
else
throw new Exception("Timed out");

How do I test a general exception in a unit test of my C# Console Application?

I'm curious as to how I can test against a general exception with unit tests, I am using MSTest for this and NSubstitute for mocking services.
This is a method from my C# Console application, it uploads a file and it's contents to Microsoft Azure. Logically all that exists is this method since I'm still in the unit testing phase but it makes the assumption the filename and the content will be passed to it when the time comes.
public async Task UploadBlob(string fileName, string content, bool overwrite = false)
{
using MemoryStream stream = new(Encoding.UTF8.GetBytes(content));
var blobClient = _container.GetBlobClient(fileName);
try
{
// Upload to azure storage
await blobClient.UploadAsync(stream, overwrite);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
You can see that we use a try/catch for the upload async portion of this method, for any number reasons this upload could fail and the exception would therefore be caught. I'm trying to mock this up in my unit test so that I can simulate this outcome but I am not sure how we can test this.
I've searched around and I couldn't find any resources with this sort of scenario, in my application I have more than once instance of a potential exception within a method, at the moment there is no code coverage for this and I'd like to have something in place that can simulate the failure.
I have started my unit test but I've hit a road block and not sure how to test
private readonly FileRepository _sut;
private readonly BlobServiceClient _bsc = Substitute.For<BlobServiceClient>();
private readonly BlobContainerClient _bcc = Substitute.For<BlobContainerClient>();
private readonly BlobClient _bc = Substitute.For<BlobClient>();
public FileRepositoryTests()
{
_bsc.GetBlobContainerClient(default).ReturnsForAnyArgs(_bcc);
_bcc.GetBlobClient(default).ReturnsForAnyArgs(_bc);
_sut = new FileRepository(_blobServiceClient);
}
[TestMethod]
public async Task UploadBlob_FailesToUpload_ThrowsException()
{
// Arrange
var fileName = "2022-01-01file.txt";
var fileContent = "Hello, world!";
// Act
...This is just calling the service but I need to simulate the exception
var result = _sut.UploadBlob(fileName, fileContent);
// Assert
...Need to assert the exception but I have to mock it first
}
Any help is appreciated
You can wrap your call in a try catch block to get the exception. You can check the type of the exception and the error message to pass the test.
[TestMethod]
public async Task UploadBlob_FailesToUpload_ThrowsException()
{
// Arrange
var fileName = "2022-01-01file.txt";
var fileContent = "Hello, world!";
Exception expectedExcetpion = null;
// Act
try
{
var result = _sut.UploadBlob(fileName, fileContent);
}
catch (Exception ex)
{
expectedExcetpion = ex;
}
// Assert
Assert.IsNotNull(expectedExcetpion);
}

Streaming an in-memory generated file in ASP.NET Core

After trawling the internet for hours, I'm lost on how to solve my problem for ASP.NET Core 2.x.
I am generating a CSV on the fly (which can take several minutes) and then trying to send that back to the client. Lots of clients are timing out before I start sending a response, so I am trying to stream the file back to them (with an immediate 200 response) and write to the stream asynchronously. It seemed like this was possible with PushStreamContent previously in ASP, but I'm unsure how to structure my code so the CSV generation is done asynchronously and returning an HTTP response immediately.
[HttpGet("csv")]
public async Task<FileStreamResult> GetCSV(long id)
{
// this stage can take 2+ mins, which obviously blocks the response
var data = await GetData(id);
var records = _csvGenerator.GenerateRecords(data);
// using the CsvHelper Nuget package
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
var csv = new CsvWriter(writer);
csv.WriteRecords(stream, records);
await writer.FlushAsync();
return new FileStreamResult(stream, new MediaTypeHeaderValue("text/csv))
{
FileDownloadName = "results.csv"
};
}
If you make a request to this controller method, you'll get nothing until the whole CSV has finished generating and then you finally get a response, by which point most client requests have timed out.
I've tried wrapping the CSV generation code in a Task.Run() but that has not helped my issue either.
There isn't a PushStreamContext kind of type built-in to ASP.NET Core. You can, however, build your own FileCallbackResult which does the same thing. This example code should do it:
public class FileCallbackResult : FileResult
{
private Func<Stream, ActionContext, Task> _callback;
public FileCallbackResult(MediaTypeHeaderValue contentType, Func<Stream, ActionContext, Task> callback)
: base(contentType?.ToString())
{
if (callback == null)
throw new ArgumentNullException(nameof(callback));
_callback = callback;
}
public override Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var executor = new FileCallbackResultExecutor(context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>());
return executor.ExecuteAsync(context, this);
}
private sealed class FileCallbackResultExecutor : FileResultExecutorBase
{
public FileCallbackResultExecutor(ILoggerFactory loggerFactory)
: base(CreateLogger<FileCallbackResultExecutor>(loggerFactory))
{
}
public Task ExecuteAsync(ActionContext context, FileCallbackResult result)
{
SetHeadersAndLog(context, result, null);
return result._callback(context.HttpContext.Response.Body, context);
}
}
}
Usage:
[HttpGet("csv")]
public IActionResult GetCSV(long id)
{
return new FileCallbackResult(new MediaTypeHeaderValue("text/csv"), async (outputStream, _) =>
{
var data = await GetData(id);
var records = _csvGenerator.GenerateRecords(data);
var writer = new StreamWriter(outputStream);
var csv = new CsvWriter(writer);
csv.WriteRecords(stream, records);
await writer.FlushAsync();
})
{
FileDownloadName = "results.csv"
};
}
Bear in mind that FileCallbackResult has the same limitations as PushStreamContext: that if an error occurs in the callback, the web server has no good way of notifying the client of that error. All you can do is propagate the exception, which will cause ASP.NET to clamp the connection shut early, so clients get a "connection unexpectedly closed" or "download aborted" error. This is because HTTP sends the error code first, in the header, before the body starts streaming.
If document generation takes 2+ minutes it should be asynchronous. It could be like this:
client sends request to generate document
you accept request, start generation in background and reply with message like generation has been started, we will notify you
on client you periodically check whether document is ready and get the link finally
You also can do it with signalr. Steps are the same but it's not needed for client to check the status of document. You can push the link when document is completed.

taskcanceledexception a task was canceled

I am receiving error
taskcanceledexception a task was canceled without any inner exception details, and I am not receiving taskcanceled exception in Sentry. How can I see what the stack trace for this exception is or what changes I need to make to the code ?
Thanks
private T CallDiffbotAndDeserialise<T>(string baseUrl, string pageUrl, int maxTags, int minimumTagConfidencePercentage)
{
var client = diffBotConnection.GetClient();
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = client.GetAsync($"?token={settings.DiffBotToken}&maxTags={maxTags}&tagConfidence={minimumTagConfidencePercentage / 100}&url={Uri.EscapeDataString(pageUrl)}&ts={DateTime.Now.ToSafeCacheString()}").Result;
string responseString = response.Content.ReadAsStringAsync().Result;
T diffBotResponse = JsonConvert.DeserializeObject<T>(responseString);
return diffBotResponse;
}
catch (AggregateException e) // If the task is cancelled or times out
{
return default(T);
};
}
API connection:
public abstract class APIConnection : IDisposable
{
protected HttpClient Client;
private bool disposed = false;
protected APIConnection() : this(3000) { }
protected APIConnection(int timeOut)
{
Client = new HttpClient()
{
Timeout = TimeSpan.FromMilliseconds(timeOut)
};
}
public HttpClient GetClient() => Client;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
Client.Dispose();
}
disposed = true;
}
}
You are calling .Result which always throws AggregateException.
That means you are not only catching TaskCancelledException or OperationCancelledException, you'll catch anything thrown by the two calls to .Result.
Since you are handling the exception and hiding the fact it ever happened (by catch and returning) Sentry won't know about it. If you want to send that event to Sentry, you'd need to call the Sentry client manually.
With SharpRaven:
var ravenClient = new RavenClient("dsn"); // Initialize the client
ravenClient.CaptureEvent(new SentryEvent(exception));
With the new SDK Sentry is developing (which is still a preview release):
// Initialize the SDK only once, at the start of the app
using (SentrySdk.Init("dsn"))
{
SentrySdk.AddBreadcrumb($"Starting a web request to: {baseUrl}");
try
{
// make request
}
catch (Exception e)
{
SentrySdk.CaptureException(exception);
}
}
In this example I added a breadcrumb which in case of an event (for example capturing an exception explicitly like above) is sent together with the event.
Also note that the new SDK automatically detects exceptions that go unhandled. That is not the case of your exemple since you are explicitly catching it.
I think it's important to mention that ideally you would avoid blocking the thread by calling .Result and instead would use async/await.
The await keyword unwraps the Exception from the faulted Task.
That means that your catch block could now catch OperationCancelledException instead. Any other error like a total failure to connect to the server would not go into your catch block and instead would bubble up the stack.

catch exception from awaited async function

I got the following:
public async Task<bool> IsAuthenticatedAsync()
{
using (HttpClient client = new HttpClient())
{
using (MultipartFormDataContent content = new MultipartFormDataContent())
{
content.Add(new StringContent(this.Username, Encoding.UTF8), "username");
content.Add(new StringContent(this.Password, Encoding.UTF8), "password");
try
{
HttpResponseMessage message = await client.PostAsync(authenticatedUrl, content);
if (message.StatusCode == HttpStatusCode.Accepted)
return true;
return false;
}
catch(HttpRequestException)
{
System.Windows.Forms.MessageBox.Show("Some text.");
}
}
}
return false;
}
where authenticatedUrl is some static Uri.
Now assuming that the webserver is not available (or the adress is wrong) await client.PostAsync(authenticatedUrl, content) throws a HttpRequestException, hence the try-catch.
Problem is that the exception is not getting caught. I tried turning off Just My Code but that just adds other exceptions (i.e. SocketException) as suggested here and still doesn't let catch handle the exception.
Why isn't the exception being caught?
Edit
main form(GUI):
public GUI(...)
{
...
CheckLoginState(username, password);
...
}
private async void CheckLoginState(string username, string password)
{
User user = new User(username, password);
if (user.Username != null && user.Password != null && await user.IsAuthenticatedAsync())
abmeldenToolStripMenuItem.Enabled = true;
}
I'm pretty sure the exception is not being caught, because it is wrapped in the task returned from the call to "CheckLoginState"-method, and since you are not awaiting that task, then your exception will never get thrown. From the looks of it, you are calling it from a constructor so I'm going to assume you can't make the "public GUI" into an "public async void GUI".
For the sake of testing, you could try a blocking wait ex:
public GUI(...)
{
...
var task = CheckLoginState(username, password).Wait();
if(task.IsFaulted && task.Exception != null)
{
throw task.Exception
}
...
}
Another way would be to let the async/await-pattern to spread naturally and tie it up to an event (Don't know if you're using wpf or winforms, so I'll assume wpf).
public GUI(...)
{
this.Loaded += async (obj, eventArgs) => await CheckLoginState(username, password);
...
}
Of course, it doesn't have to be the "loaded" event. Point is, that an event can be async, a constructor can't (from what I know anyway).

Categories

Resources