I'm calling an external API and would like my API to be unit testable. And to do that, i'm trying to wrap HttpClient. I only need one method for now.
Here is my interface.
public interface IHttpClient
{
Task<string> GetStringAsync(string url);
}
And this is how I implemented it.
public class HttpClientWrapper : IHttpClient
{
private readonly HttpClient _httpClient;
public HttpClientWrapper()
{
// I could also inject this but I think this will be fine as is.
_httpClient = new HttpClient(new HttpClientHandler(), false);
}
public async Task<string> GetStringAsync(string url)
{
//validate url here
return await _httpClient.GetStringAsync(url);
}
}
Doubts I have? is this the right way to do it? Will setting the bool parameter result in resource leaking here? I read a couple of conflicting ideas about whether HttpClient has to be disposed on every call or not. I took, the not disposing side but am not really quite certain though.
If there is a way to use HttpClient without having a wrapper but make the API testable, that will be great too. But so far, i failed to get that working.
Thanks,
CleanKoder
While it could still be nice to create an interface for the client, the HttpClient class is actually designed with testability in mind! When instantiating your HttpClient you can inject a custom HttpMessageHandler. By overriding Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in this class, you can interrupt all requests before they are actually written to the socket, inspecting them and returning whatever you see fit.
Here is an example of such a test double I wrote in a project, feel free to modify it to suit your needs:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public HttpRequestMessage LastRequest;
public string LastRequestString = string.Empty;
public string ResponseContent = string.Empty;
public HttpStatusCode ResponseStatusCode = HttpStatusCode.OK;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Content != null)
{
LastRequestString = await request.Content.ReadAsStringAsync();
}
LastRequest = request;
return await Task.FromResult(new HttpResponseMessage
{
StatusCode = ResponseStatusCode,
Content = new StringContent(ResponseContent)
});
}
}
You could also use some isolation framework like NSubstitute if you think that's more appropriate for your project.
Related
I'm trying to push request/response into elasticsearch but I'm stuck after reading documentation when trying to get body of response. It's stated there: "While enabling buffering is possible, it's discouraged as it can add significant memory and latency overhead. Using a wrapped, streaming approach is recommended if the body must be examined or modified".
So this part is quite understandable since buffered response might be saved into file.
"See the ResponseCompression middleware for an example." (Full article)
I checked what is in there and I'm stuck. Should I create class that implements IHttpResponseBodyFeature?
I've implemented simple class that implements that interface:
internal class BodyReader : IHttpResponseBodyFeature, IDisposable
{
private bool _disposedValue;
public Stream Stream { get; } = new MemoryStream();
public PipeWriter Writer => throw new NotImplementedException();
public Task CompleteAsync()
{
return Task.CompletedTask;
}
public void DisableBuffering()
{
//throw new NotImplementedException();
}
public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task StartAsync(CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
Stream?.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~Tmp()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
And then in middleware:
var bodyReader = new BodyReader();
context.Features.Set<IHttpResponseBodyFeature>(bodyReader);
try
{
await _next(context);
bodyReader.Stream.Position = 0;
using (var sr = new StreamReader(bodyReader.Stream))
{
// here should be text response but unfortunately in variable is some garbage
// I'm guessing ciphered response?
var html = sr.ReadToEnd();
}
bodyReader.Dispose();
}
finally
{
context.Features.Set(originalBodyFeature);
}
Seems that in html variable is some garbage - maybe ciphered? Also don't have an idea how to push response into pipe once again.
I'm not sure if approach is good? Maybe I shouldn't use middleware to logging or my implementation of IHttpResponseBodyFeature is incorrect?
Either way I need to push into elastic both request and response :)
I asked about this on yarp's github and I got information that this is not because of https but compression (I simply forgot about it):
https://github.com/microsoft/reverse-proxy/issues/1921#issuecomment-1301287432
Long story short it was enough to add:
builder.Services.AddReverseProxy()
.ConfigureHttpClient((context, handler) =>
{
// this is required to decompress automatically
handler.AutomaticDecompression = System.Net.DecompressionMethods.All;
})
Happy coding :)
I have been trying to mock/fake the static method FindAsync() in my Unit test cases using Wrappers, some concepts of Pose.
As the static methods cannot be mocked or faked normally.
It is not successful.
The code in the repository layer which I want to unit test points to the IMongoCollectionExtension.FindAsync() method.
This is the method I am trying to mock
public async Task<MyClass> GetItem(Guid id)
{
var filter = Builders<MyClass>.Filter.Eq(m => m.Id, id);
var result = await _context.MyCollection.FindAsync(filter);
return result.FirstOrDefault();
}
This FindAsync() is pointing to IMongoCollectionExtensions STATIC class
public static Task<IAsyncCursor<TDocument>> FindAsync<TDocument>(this IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filter, FindOptions<TDocument, TDocument> options = null, CancellationToken cancellationToken = default(CancellationToken));
So as it is pointing to Static class and a static method I started writing wrapper to mock,
First Method tried using Wrapper:
This is wrapper I have created.
public interface IMongoCollectionExtensionsWrapper
{
Task<IAsyncCursor<MyClass>> FindAsync<MyClass>(IMongoCollection<MyClass> collection, FilterDefinition<MyClass> filter, FindOptions<MyClass, MyClass> options = null, CancellationToken cancellationToken = default(CancellationToken));
}
public class MongoCollectionExtensionsWrapper : IMongoCollectionExtensionsWrapper
{
public Task<IAsyncCursor<MyClass>> FindAsync<MyClass>(IMongoCollection<MyClass> collection, FilterDefinition<MyClass> filter, FindOptions<MyClass, MyClass> options = null, CancellationToken cancellationToken = default(CancellationToken))
{
return collection.FindAsync(filter, options, cancellationToken);
}
}
public static class FakeExtensions
{
public static IMongoCollectionExtensionsWrapper defaultmcExtWrapper = new MongoCollectionExtensionsWrapper();
public static Task<IAsyncCursor<MyClass>> FindAsync(this IMongoCollection<MyClass> collection, FilterDefinition<MyClass> filter, FindOptions<MyClass, MyClass> options = null, CancellationToken cancellationToken = default(CancellationToken))
{
return defaultmcExtWrapper.FindAsync(collection, filter, options, cancellationToken);
}
}
As the wrapper was not working properly i checked out free framework Pose to mock static methods. That was not successful too.
Second trial using Pose
Shim findshim = Shim.Replace(() => IMongoCollectionExtensions.FindAsync(Is.A<IMongoCollection<MyClass>>(), filter, null, CancellationToken.None)).With(delegate (IMongoCollection<MyClass> mc, FilterDefinition<MyClass> f, FindOptions<MyClass, MyClass> o, CancellationToken ca) { return Task.FromResult(_fakeOutput.FakedObject); });
NOTE: _fakeOutput is a faked Cursor holding an IEnumerable. It works fine.
PoseContext.Isolate(() =>
{
Task.Run(() =>
{
var exp = Task.FromResult(item1);
var myres = _Repo.GetItem(Id);
Assert.Equal(exp, myres);
});
}, findshim);
var myres = _Repo.GetItem(Id);
In both the trials, I have tried mocking IMongoCollectionExtensions.FindAsync() but result (output of the method i want to unit test after setting up mock/fake) in both cases are null
and when I tried below Assertion if the FindAsync() method of IMongoCollectionExtension has Happened or not, but it didn't hit. I dont understand when the method i want to unit test is pointing to IMongoCollectionExtension.FindAsync() only but it is not hitting.
fakeIMongoCollExt.CallsTo(x => x.FindAsync(A<IMongoCollection<MyClass>>.Ignored, A<FilterDefinition<MyClass>>.Ignored, null, CancellationToken.None)).MustHaveHappened();
(Method signature has MongoCollections as first parameter - Extension Method)
is showing that it didn't hit that method.
So I tried checking MustHaveHappened() for IMongoCollection.FindAsync() (It is interface method not the static class method which we are discussing above) which also tells that "The target of this call is not the fake object being configured."
I am not sure how FindAsync() is pointing. How to proceed with unit test cases. Please let me know if you have any idea.. Thanks in Advance..
IMongoCollections.FindAsync() Mocking
I have an endpoint which is receiving IPN activity from PayPal. Here is the POST Action that is taken straight from their docs with manual modifications:
[HttpPost]
public IActionResult Receive()
{
IPNContext ipnContext = new IPNContext()
{
IPNRequest = Request
};
using (StreamReader reader = new StreamReader(ipnContext.IPNRequest.Body, Encoding.ASCII))
{
ipnContext.RequestBody = reader.ReadToEnd();
}
List<KeyValuePair<string, string>> ipnVarsWithCmd = ipnContext.RequestBody.Split('&')
.Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
//Fire and forget verification task -- ** THIS **
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
//Reply back a 200 code
return Ok();
}
The issue is the indicated line. This is a "fire and forget" route, and is executed asynchronously. When the Action is complete, and returns Ok, I am assuming that the injected Entity Framework context from the controller:
public class IPNController : Controller
{
private readonly EFContext _context;
public IPNController(EFContext context)
{
_context = context;
}
}
... gets Disposed? According to my logs, it looks like it.
Meanwhile, I have that second thread doing the actual legwork of the IPN request which needs that EFContext to be around.
Is there a pattern I am missing here? (Bearing in mind whilst I'm not new to .NET I am to .NET Core)
Or is there a way I can "get it back" so I can use it?
Update:
You might find my initialisation of the context useful:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<EFContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
}
Change:
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
to
await Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
and method declaration to:
public async Task<IActionResult> Receive()
Also wrap IPNContext to using block to let it dispose when it is not needed.
Is it acceptable to re-use a reference to a IReliableCollection or should I request from IReliableStateManager every time I want to use it?
For example, if I have a dictionary that is widely used in my application, is it acceptable to retrieve it once in the RunAsync method and then pass this reference to any method that requires it, e.g:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
_someCollection = StateManager.GetOrAddAsync<IReliableDictionary<int, string>>(
"SomeName");
}
public async Task DoSomething(int id, string message)
{
_someClass.DoSomething(_someCollection, id, message);
}
And then use in a class like so:
public class SomeClass
{
public void DoSomething(IReliableDictionary<int, string> dict, int id, string msg)
{
using (ITransaction tx = StateManager.CreateTransaction())
{
await dict.AddAsync(tx, id, msg);
await tx.CommitAsync();
}
}
}
Or should I request from IReliableStateManager on each call, e.g,
public class SomeClass
{
public void DoSomething(int id, string msg)
{
var dict = StateManager.GetOrAddAsync<IReliableDictionary<int, string>>("SomeName");
using (ITransaction tx = StateManager.CreateTransaction())
{
await dict.AddAsync(tx, id, msg);
await tx.CommitAsync();
}
}
}
Passing a reference seems to work fine from what I can tell but I'm not sure whether this would be considered bad practice and I can't find a definitive answer in the guidelines or documentation
Yes. You can.
You can check this to see how you can even receive notifications when new instance of IReliableState is added to ReliableStateManager.
You can also subscribe for events (if this is a dictionary).
I have a REST endpoint implemented with Spring MVC #RestController. Sometime, depends on input parameters in my controller I need to send http redirect on client.
Is it possible with Spring MVC #RestController and if so, could you please show an example ?
Add an HttpServletResponse parameter to your Handler Method then call response.sendRedirect("some-url");
Something like:
#RestController
public class FooController {
#RequestMapping("/foo")
void handleFoo(HttpServletResponse response) throws IOException {
response.sendRedirect("some-url");
}
}
To avoid any direct dependency on HttpServletRequest or HttpServletResponse I suggest a "pure Spring" implementation returning a ResponseEntity like this:
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(newUrl));
return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
If your method always returns a redirect, use ResponseEntity<Void>, otherwise whatever is returned normally as generic type.
Came across this question and was surprised that no-one mentioned RedirectView. I have just tested it, and you can solve this in a clean 100% spring way with:
#RestController
public class FooController {
#RequestMapping("/foo")
public RedirectView handleFoo() {
return new RedirectView("some-url");
}
}
redirect means http code 302, which means Found in springMVC.
Here is an util method, which could be placed in some kind of BaseController:
protected ResponseEntity found(HttpServletResponse response, String url) throws IOException { // 302, found, redirect,
response.sendRedirect(url);
return null;
}
But sometimes might want to return http code 301 instead, which means moved permanently.
In that case, here is the util method:
protected ResponseEntity movedPermanently(HttpServletResponse response, String url) { // 301, moved permanently,
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, url).build();
}
As the redirections are usually needed in a not-straightforward path, I think throwing an exception and handling it later is my favourite solution.
Using a ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
NotLoggedInException.class
})
protected ResponseEntity<Object> handleNotLoggedIn(
final NotLoggedInException ex, final WebRequest request
) {
final String bodyOfResponse = ex.getMessage();
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", ex.getRedirectUri());
return handleExceptionInternal(
ex, bodyOfResponse,
headers, HttpStatus.FOUND, request
);
}
}
The exception class in my case:
#Getter
public class NotLoggedInException extends RuntimeException {
private static final long serialVersionUID = -4900004519786666447L;
String redirectUri;
public NotLoggedInException(final String message, final String uri) {
super(message);
redirectUri = uri;
}
}
And I trigger it like this:
if (null == remoteUser)
throw new NotLoggedInException("please log in", LOGIN_URL);
if you #RestController returns an String you can use something like this
return "redirect:/other/controller/";
and this kind of redirect is only for GET request, if you want to use other type of request use HttpServletResponse