Making sure application is requesting resources correctly











up vote
1
down vote

favorite












Some of my applications use ASP.NET Core REST Web Services. Testing a Web Service is actually quite easy but this does not apply to the client. I not only want to be sure that my Web Services work as expected but also that the clients are sending correct requests. Because I didn't find anything that would help me do this, I built my own framework with Kestrel. It's composed of the following components:



Middleware



The request middleware logs all incomming requests by path in a dictionary and some metadata about it that I later use with Assert.



In this experiment it copies the body stream, headers and the path. I wasn't sure what I should do when the middleware throws an exception. Return a StatusCode or throw? I chose the former.



public class RequestLoggerMiddleware
{
private readonly RequestDelegate _next;

private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

public RequestLoggerMiddleware(RequestDelegate next, ConcurrentDictionary<PathString, List<RequestInfo>> requests)
{
_next = next;
_requests = requests;
}

public async Task Invoke(HttpContext context)
{
try
{
// We'll need this later so don't dispose.
var memory = new MemoryStream();
{
// It needs to be copied because otherwise it'll get disposed.
await context.Request.Body.CopyToAsync(memory);

var request = new RequestInfo
{
Path = context.Request.Path,
ContentLength = context.Request.ContentLength,
// There is no copy-constructor.
Headers = new HeaderDictionary(context.Request.Headers.ToDictionary(x => x.Key, x => x.Value)),
Body = memory
};

_requests.AddOrUpdate
(
context.Request.Path,
path => new List<RequestInfo> { request },
(path, requests) =>
{
requests.Add(request);
return requests;
});

await _next(context);
context.Response.StatusCode = StatusCodes.Status418ImATeapot;
}

}
catch (Exception inner)
{
// Not sure what to do... throw or not?
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
//throw;
}
}
}


Server



The middleware is registred with the Startup class via a helper extension:



public class RequestLoggerStartup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<RequestLoggerMiddleware>();
}
}


I then initialize the server with it and let it run in a separate thread. It exposes an indexer for getting requests by path.



public class RequestLogger : IDisposable
{
private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

private readonly IWebHost _host;

public RequestLogger(string url)
{
_requests = new ConcurrentDictionary<PathString, List<RequestInfo>>();

_host =
WebHost
.CreateDefaultBuilder()
.UseUrls(url)
.UseRequests(_requests)
.UseStartup<RequestLoggerStartup>()
.Build();


Task = Task.Factory.StartNew(async () =>
{
await _host.RunAsync();
});
}

public Task Task { get; set; }

[NotNull, ItemNotNull]
public IEnumerable<RequestInfo> this[PathString path] => _requests[path];

public void Dispose()
{
_host.Dispose();
foreach (var request in _requests.SelectMany(r => r.Value))
{
request.Body?.Dispose();
}
}
}


The DTO for logging is:



public class RequestInfo
{
public long? ContentLength { get; set; }

public PathString Path { get; set; }

public IHeaderDictionary Headers { get; set; }

public MemoryStream Body { get; set; }
}




Testing



Since this framework is ment for (integration-)testing and I use XUnit, I created a fixture for injecting the server into the test-class:



public class RequestLoggerFixture : IDisposable
{
public RequestLoggerFixture()
{
Server = new RequestLogger("http://localhost:12000");
}

public RequestLogger Server { get; }

public void Dispose() => Server.Dispose();
}


Here I test the Api-Version and some of the json DTO properties and values.



using static Assert;

public class RequestExperiment : IClassFixture<RequestLoggerFixture>
{
private readonly RequestLogger _server;

public RequestExperiment(RequestLoggerFixture requestLogger)
{
_server = requestLogger.Server;
}

[Fact]
public async Task PostsGreeting()
{
// Please ignore this region while reviewing. It's for presentation purposes.
#region Request made by the applicaation somewhere deep down the rabbit hole

var client = TestClient.Create("http://localhost:12000/api", headers => { headers.AcceptJson(); });

try
{
await client.Resource("test?param=true").Configure(context =>
{
context.RequestHeadersActions.Add(headers => headers.Add("Api-Version", "1.0"));
context.Body = new { Greeting = "Hallo" };
})
.PostAsync<object>();
}
catch (Exception)
{
// client will throw because of the 418 status code
}

#endregion

// The application did it's job, now check what it requested:

var request = _server["/test?param=true"].First();

request.HasApiVersion("1.0");
request.HasProperty("$.Greeting");
request.PropertyEqual("$.Greeting", "Hallo");
}
}


These are the extensions used for asserting in this experiment. I will create more of them where necessary while using it. This should be just a proof-of-concept.



public static class RequestLogExtensions
{
public static void HasProperty(this RequestInfo request, string jsonPath)
{
False(request.ToJson().SelectToken(jsonPath) == null, $"Property '{jsonPath}' not found.");
}

public static void PropertyEqual(this RequestInfo request, string jsonPath, object expected)
{
if (request.ToJson().SelectToken(jsonPath) is JValue actual)
{
True(actual.Equals(actual), $"Property '{jsonPath}' value '{actual.Value}' does not equal '{expected}'.");
}
}

public static void HasApiVersion(this RequestInfo request, string version)
{
if (request.Headers.TryGetValue("Api-Version", out var value))
{
True(value.Equals(version));
}
else
{
False(true, $"Invalid version. Expected: '{version}', Actual: '{value}'");
}
}
}




In my applications I use my own RestClient that was also on Code Review here. I used it to create a test-client for sending requests that I post for reference purposes.




public interface ITestClient : IRestClient { }

public class TestClient : ITestClient
{
private readonly IRestClient _restClient;

private TestClient(IRestClient restClient)
{
_restClient = restClient;
}

public string BaseUri => _restClient.BaseUri;

public static ITestClient Create(string baseUri, Action<HttpRequestHeaders> configureDefaultRequestHeaders)
{
var restClient = new RestClient(baseUri, configureDefaultRequestHeaders);
return new TestClient(restClient);
}

public Task<T> InvokeAsync<T>(HttpMethodContext context, CancellationToken cancellationToken)
{
return _restClient.InvokeAsync<T>(context, cancellationToken);
}
}





What do you think of this framework? I seems to do its job well but can it do it better? The server is very simple but is this enough or have I missed anything? Convenience and simplicity are the main goals.










share|improve this question
























  • Its GitHub home is here.
    – t3chb0t
    yesterday










  • Tell me the difference between Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); }); and Task = host.RunAsync();.
    – Jesse C. Slicer
    yesterday










  • @JesseC.Slicer I thought it runs the Task on a new thread-pool thread but all you can find here is that one shouldn't use it because... it's dangerous, wow, what a wonderful explanation :-| but maybe it's not necessary after all. I'm not entirely sure - that's why I've posted this experiment ;-)
    – t3chb0t
    yesterday












  • I ask because I truly have no idea and I'm dealing with a production issue at the day job that has similar code and stuff is going off into la-la land.
    – Jesse C. Slicer
    yesterday






  • 1




    @JesseC.Slicer this answer recommends using Task.Run for starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
    – t3chb0t
    yesterday

















up vote
1
down vote

favorite












Some of my applications use ASP.NET Core REST Web Services. Testing a Web Service is actually quite easy but this does not apply to the client. I not only want to be sure that my Web Services work as expected but also that the clients are sending correct requests. Because I didn't find anything that would help me do this, I built my own framework with Kestrel. It's composed of the following components:



Middleware



The request middleware logs all incomming requests by path in a dictionary and some metadata about it that I later use with Assert.



In this experiment it copies the body stream, headers and the path. I wasn't sure what I should do when the middleware throws an exception. Return a StatusCode or throw? I chose the former.



public class RequestLoggerMiddleware
{
private readonly RequestDelegate _next;

private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

public RequestLoggerMiddleware(RequestDelegate next, ConcurrentDictionary<PathString, List<RequestInfo>> requests)
{
_next = next;
_requests = requests;
}

public async Task Invoke(HttpContext context)
{
try
{
// We'll need this later so don't dispose.
var memory = new MemoryStream();
{
// It needs to be copied because otherwise it'll get disposed.
await context.Request.Body.CopyToAsync(memory);

var request = new RequestInfo
{
Path = context.Request.Path,
ContentLength = context.Request.ContentLength,
// There is no copy-constructor.
Headers = new HeaderDictionary(context.Request.Headers.ToDictionary(x => x.Key, x => x.Value)),
Body = memory
};

_requests.AddOrUpdate
(
context.Request.Path,
path => new List<RequestInfo> { request },
(path, requests) =>
{
requests.Add(request);
return requests;
});

await _next(context);
context.Response.StatusCode = StatusCodes.Status418ImATeapot;
}

}
catch (Exception inner)
{
// Not sure what to do... throw or not?
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
//throw;
}
}
}


Server



The middleware is registred with the Startup class via a helper extension:



public class RequestLoggerStartup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<RequestLoggerMiddleware>();
}
}


I then initialize the server with it and let it run in a separate thread. It exposes an indexer for getting requests by path.



public class RequestLogger : IDisposable
{
private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

private readonly IWebHost _host;

public RequestLogger(string url)
{
_requests = new ConcurrentDictionary<PathString, List<RequestInfo>>();

_host =
WebHost
.CreateDefaultBuilder()
.UseUrls(url)
.UseRequests(_requests)
.UseStartup<RequestLoggerStartup>()
.Build();


Task = Task.Factory.StartNew(async () =>
{
await _host.RunAsync();
});
}

public Task Task { get; set; }

[NotNull, ItemNotNull]
public IEnumerable<RequestInfo> this[PathString path] => _requests[path];

public void Dispose()
{
_host.Dispose();
foreach (var request in _requests.SelectMany(r => r.Value))
{
request.Body?.Dispose();
}
}
}


The DTO for logging is:



public class RequestInfo
{
public long? ContentLength { get; set; }

public PathString Path { get; set; }

public IHeaderDictionary Headers { get; set; }

public MemoryStream Body { get; set; }
}




Testing



Since this framework is ment for (integration-)testing and I use XUnit, I created a fixture for injecting the server into the test-class:



public class RequestLoggerFixture : IDisposable
{
public RequestLoggerFixture()
{
Server = new RequestLogger("http://localhost:12000");
}

public RequestLogger Server { get; }

public void Dispose() => Server.Dispose();
}


Here I test the Api-Version and some of the json DTO properties and values.



using static Assert;

public class RequestExperiment : IClassFixture<RequestLoggerFixture>
{
private readonly RequestLogger _server;

public RequestExperiment(RequestLoggerFixture requestLogger)
{
_server = requestLogger.Server;
}

[Fact]
public async Task PostsGreeting()
{
// Please ignore this region while reviewing. It's for presentation purposes.
#region Request made by the applicaation somewhere deep down the rabbit hole

var client = TestClient.Create("http://localhost:12000/api", headers => { headers.AcceptJson(); });

try
{
await client.Resource("test?param=true").Configure(context =>
{
context.RequestHeadersActions.Add(headers => headers.Add("Api-Version", "1.0"));
context.Body = new { Greeting = "Hallo" };
})
.PostAsync<object>();
}
catch (Exception)
{
// client will throw because of the 418 status code
}

#endregion

// The application did it's job, now check what it requested:

var request = _server["/test?param=true"].First();

request.HasApiVersion("1.0");
request.HasProperty("$.Greeting");
request.PropertyEqual("$.Greeting", "Hallo");
}
}


These are the extensions used for asserting in this experiment. I will create more of them where necessary while using it. This should be just a proof-of-concept.



public static class RequestLogExtensions
{
public static void HasProperty(this RequestInfo request, string jsonPath)
{
False(request.ToJson().SelectToken(jsonPath) == null, $"Property '{jsonPath}' not found.");
}

public static void PropertyEqual(this RequestInfo request, string jsonPath, object expected)
{
if (request.ToJson().SelectToken(jsonPath) is JValue actual)
{
True(actual.Equals(actual), $"Property '{jsonPath}' value '{actual.Value}' does not equal '{expected}'.");
}
}

public static void HasApiVersion(this RequestInfo request, string version)
{
if (request.Headers.TryGetValue("Api-Version", out var value))
{
True(value.Equals(version));
}
else
{
False(true, $"Invalid version. Expected: '{version}', Actual: '{value}'");
}
}
}




In my applications I use my own RestClient that was also on Code Review here. I used it to create a test-client for sending requests that I post for reference purposes.




public interface ITestClient : IRestClient { }

public class TestClient : ITestClient
{
private readonly IRestClient _restClient;

private TestClient(IRestClient restClient)
{
_restClient = restClient;
}

public string BaseUri => _restClient.BaseUri;

public static ITestClient Create(string baseUri, Action<HttpRequestHeaders> configureDefaultRequestHeaders)
{
var restClient = new RestClient(baseUri, configureDefaultRequestHeaders);
return new TestClient(restClient);
}

public Task<T> InvokeAsync<T>(HttpMethodContext context, CancellationToken cancellationToken)
{
return _restClient.InvokeAsync<T>(context, cancellationToken);
}
}





What do you think of this framework? I seems to do its job well but can it do it better? The server is very simple but is this enough or have I missed anything? Convenience and simplicity are the main goals.










share|improve this question
























  • Its GitHub home is here.
    – t3chb0t
    yesterday










  • Tell me the difference between Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); }); and Task = host.RunAsync();.
    – Jesse C. Slicer
    yesterday










  • @JesseC.Slicer I thought it runs the Task on a new thread-pool thread but all you can find here is that one shouldn't use it because... it's dangerous, wow, what a wonderful explanation :-| but maybe it's not necessary after all. I'm not entirely sure - that's why I've posted this experiment ;-)
    – t3chb0t
    yesterday












  • I ask because I truly have no idea and I'm dealing with a production issue at the day job that has similar code and stuff is going off into la-la land.
    – Jesse C. Slicer
    yesterday






  • 1




    @JesseC.Slicer this answer recommends using Task.Run for starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
    – t3chb0t
    yesterday















up vote
1
down vote

favorite









up vote
1
down vote

favorite











Some of my applications use ASP.NET Core REST Web Services. Testing a Web Service is actually quite easy but this does not apply to the client. I not only want to be sure that my Web Services work as expected but also that the clients are sending correct requests. Because I didn't find anything that would help me do this, I built my own framework with Kestrel. It's composed of the following components:



Middleware



The request middleware logs all incomming requests by path in a dictionary and some metadata about it that I later use with Assert.



In this experiment it copies the body stream, headers and the path. I wasn't sure what I should do when the middleware throws an exception. Return a StatusCode or throw? I chose the former.



public class RequestLoggerMiddleware
{
private readonly RequestDelegate _next;

private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

public RequestLoggerMiddleware(RequestDelegate next, ConcurrentDictionary<PathString, List<RequestInfo>> requests)
{
_next = next;
_requests = requests;
}

public async Task Invoke(HttpContext context)
{
try
{
// We'll need this later so don't dispose.
var memory = new MemoryStream();
{
// It needs to be copied because otherwise it'll get disposed.
await context.Request.Body.CopyToAsync(memory);

var request = new RequestInfo
{
Path = context.Request.Path,
ContentLength = context.Request.ContentLength,
// There is no copy-constructor.
Headers = new HeaderDictionary(context.Request.Headers.ToDictionary(x => x.Key, x => x.Value)),
Body = memory
};

_requests.AddOrUpdate
(
context.Request.Path,
path => new List<RequestInfo> { request },
(path, requests) =>
{
requests.Add(request);
return requests;
});

await _next(context);
context.Response.StatusCode = StatusCodes.Status418ImATeapot;
}

}
catch (Exception inner)
{
// Not sure what to do... throw or not?
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
//throw;
}
}
}


Server



The middleware is registred with the Startup class via a helper extension:



public class RequestLoggerStartup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<RequestLoggerMiddleware>();
}
}


I then initialize the server with it and let it run in a separate thread. It exposes an indexer for getting requests by path.



public class RequestLogger : IDisposable
{
private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

private readonly IWebHost _host;

public RequestLogger(string url)
{
_requests = new ConcurrentDictionary<PathString, List<RequestInfo>>();

_host =
WebHost
.CreateDefaultBuilder()
.UseUrls(url)
.UseRequests(_requests)
.UseStartup<RequestLoggerStartup>()
.Build();


Task = Task.Factory.StartNew(async () =>
{
await _host.RunAsync();
});
}

public Task Task { get; set; }

[NotNull, ItemNotNull]
public IEnumerable<RequestInfo> this[PathString path] => _requests[path];

public void Dispose()
{
_host.Dispose();
foreach (var request in _requests.SelectMany(r => r.Value))
{
request.Body?.Dispose();
}
}
}


The DTO for logging is:



public class RequestInfo
{
public long? ContentLength { get; set; }

public PathString Path { get; set; }

public IHeaderDictionary Headers { get; set; }

public MemoryStream Body { get; set; }
}




Testing



Since this framework is ment for (integration-)testing and I use XUnit, I created a fixture for injecting the server into the test-class:



public class RequestLoggerFixture : IDisposable
{
public RequestLoggerFixture()
{
Server = new RequestLogger("http://localhost:12000");
}

public RequestLogger Server { get; }

public void Dispose() => Server.Dispose();
}


Here I test the Api-Version and some of the json DTO properties and values.



using static Assert;

public class RequestExperiment : IClassFixture<RequestLoggerFixture>
{
private readonly RequestLogger _server;

public RequestExperiment(RequestLoggerFixture requestLogger)
{
_server = requestLogger.Server;
}

[Fact]
public async Task PostsGreeting()
{
// Please ignore this region while reviewing. It's for presentation purposes.
#region Request made by the applicaation somewhere deep down the rabbit hole

var client = TestClient.Create("http://localhost:12000/api", headers => { headers.AcceptJson(); });

try
{
await client.Resource("test?param=true").Configure(context =>
{
context.RequestHeadersActions.Add(headers => headers.Add("Api-Version", "1.0"));
context.Body = new { Greeting = "Hallo" };
})
.PostAsync<object>();
}
catch (Exception)
{
// client will throw because of the 418 status code
}

#endregion

// The application did it's job, now check what it requested:

var request = _server["/test?param=true"].First();

request.HasApiVersion("1.0");
request.HasProperty("$.Greeting");
request.PropertyEqual("$.Greeting", "Hallo");
}
}


These are the extensions used for asserting in this experiment. I will create more of them where necessary while using it. This should be just a proof-of-concept.



public static class RequestLogExtensions
{
public static void HasProperty(this RequestInfo request, string jsonPath)
{
False(request.ToJson().SelectToken(jsonPath) == null, $"Property '{jsonPath}' not found.");
}

public static void PropertyEqual(this RequestInfo request, string jsonPath, object expected)
{
if (request.ToJson().SelectToken(jsonPath) is JValue actual)
{
True(actual.Equals(actual), $"Property '{jsonPath}' value '{actual.Value}' does not equal '{expected}'.");
}
}

public static void HasApiVersion(this RequestInfo request, string version)
{
if (request.Headers.TryGetValue("Api-Version", out var value))
{
True(value.Equals(version));
}
else
{
False(true, $"Invalid version. Expected: '{version}', Actual: '{value}'");
}
}
}




In my applications I use my own RestClient that was also on Code Review here. I used it to create a test-client for sending requests that I post for reference purposes.




public interface ITestClient : IRestClient { }

public class TestClient : ITestClient
{
private readonly IRestClient _restClient;

private TestClient(IRestClient restClient)
{
_restClient = restClient;
}

public string BaseUri => _restClient.BaseUri;

public static ITestClient Create(string baseUri, Action<HttpRequestHeaders> configureDefaultRequestHeaders)
{
var restClient = new RestClient(baseUri, configureDefaultRequestHeaders);
return new TestClient(restClient);
}

public Task<T> InvokeAsync<T>(HttpMethodContext context, CancellationToken cancellationToken)
{
return _restClient.InvokeAsync<T>(context, cancellationToken);
}
}





What do you think of this framework? I seems to do its job well but can it do it better? The server is very simple but is this enough or have I missed anything? Convenience and simplicity are the main goals.










share|improve this question















Some of my applications use ASP.NET Core REST Web Services. Testing a Web Service is actually quite easy but this does not apply to the client. I not only want to be sure that my Web Services work as expected but also that the clients are sending correct requests. Because I didn't find anything that would help me do this, I built my own framework with Kestrel. It's composed of the following components:



Middleware



The request middleware logs all incomming requests by path in a dictionary and some metadata about it that I later use with Assert.



In this experiment it copies the body stream, headers and the path. I wasn't sure what I should do when the middleware throws an exception. Return a StatusCode or throw? I chose the former.



public class RequestLoggerMiddleware
{
private readonly RequestDelegate _next;

private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

public RequestLoggerMiddleware(RequestDelegate next, ConcurrentDictionary<PathString, List<RequestInfo>> requests)
{
_next = next;
_requests = requests;
}

public async Task Invoke(HttpContext context)
{
try
{
// We'll need this later so don't dispose.
var memory = new MemoryStream();
{
// It needs to be copied because otherwise it'll get disposed.
await context.Request.Body.CopyToAsync(memory);

var request = new RequestInfo
{
Path = context.Request.Path,
ContentLength = context.Request.ContentLength,
// There is no copy-constructor.
Headers = new HeaderDictionary(context.Request.Headers.ToDictionary(x => x.Key, x => x.Value)),
Body = memory
};

_requests.AddOrUpdate
(
context.Request.Path,
path => new List<RequestInfo> { request },
(path, requests) =>
{
requests.Add(request);
return requests;
});

await _next(context);
context.Response.StatusCode = StatusCodes.Status418ImATeapot;
}

}
catch (Exception inner)
{
// Not sure what to do... throw or not?
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
//throw;
}
}
}


Server



The middleware is registred with the Startup class via a helper extension:



public class RequestLoggerStartup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<RequestLoggerMiddleware>();
}
}


I then initialize the server with it and let it run in a separate thread. It exposes an indexer for getting requests by path.



public class RequestLogger : IDisposable
{
private readonly ConcurrentDictionary<PathString, List<RequestInfo>> _requests;

private readonly IWebHost _host;

public RequestLogger(string url)
{
_requests = new ConcurrentDictionary<PathString, List<RequestInfo>>();

_host =
WebHost
.CreateDefaultBuilder()
.UseUrls(url)
.UseRequests(_requests)
.UseStartup<RequestLoggerStartup>()
.Build();


Task = Task.Factory.StartNew(async () =>
{
await _host.RunAsync();
});
}

public Task Task { get; set; }

[NotNull, ItemNotNull]
public IEnumerable<RequestInfo> this[PathString path] => _requests[path];

public void Dispose()
{
_host.Dispose();
foreach (var request in _requests.SelectMany(r => r.Value))
{
request.Body?.Dispose();
}
}
}


The DTO for logging is:



public class RequestInfo
{
public long? ContentLength { get; set; }

public PathString Path { get; set; }

public IHeaderDictionary Headers { get; set; }

public MemoryStream Body { get; set; }
}




Testing



Since this framework is ment for (integration-)testing and I use XUnit, I created a fixture for injecting the server into the test-class:



public class RequestLoggerFixture : IDisposable
{
public RequestLoggerFixture()
{
Server = new RequestLogger("http://localhost:12000");
}

public RequestLogger Server { get; }

public void Dispose() => Server.Dispose();
}


Here I test the Api-Version and some of the json DTO properties and values.



using static Assert;

public class RequestExperiment : IClassFixture<RequestLoggerFixture>
{
private readonly RequestLogger _server;

public RequestExperiment(RequestLoggerFixture requestLogger)
{
_server = requestLogger.Server;
}

[Fact]
public async Task PostsGreeting()
{
// Please ignore this region while reviewing. It's for presentation purposes.
#region Request made by the applicaation somewhere deep down the rabbit hole

var client = TestClient.Create("http://localhost:12000/api", headers => { headers.AcceptJson(); });

try
{
await client.Resource("test?param=true").Configure(context =>
{
context.RequestHeadersActions.Add(headers => headers.Add("Api-Version", "1.0"));
context.Body = new { Greeting = "Hallo" };
})
.PostAsync<object>();
}
catch (Exception)
{
// client will throw because of the 418 status code
}

#endregion

// The application did it's job, now check what it requested:

var request = _server["/test?param=true"].First();

request.HasApiVersion("1.0");
request.HasProperty("$.Greeting");
request.PropertyEqual("$.Greeting", "Hallo");
}
}


These are the extensions used for asserting in this experiment. I will create more of them where necessary while using it. This should be just a proof-of-concept.



public static class RequestLogExtensions
{
public static void HasProperty(this RequestInfo request, string jsonPath)
{
False(request.ToJson().SelectToken(jsonPath) == null, $"Property '{jsonPath}' not found.");
}

public static void PropertyEqual(this RequestInfo request, string jsonPath, object expected)
{
if (request.ToJson().SelectToken(jsonPath) is JValue actual)
{
True(actual.Equals(actual), $"Property '{jsonPath}' value '{actual.Value}' does not equal '{expected}'.");
}
}

public static void HasApiVersion(this RequestInfo request, string version)
{
if (request.Headers.TryGetValue("Api-Version", out var value))
{
True(value.Equals(version));
}
else
{
False(true, $"Invalid version. Expected: '{version}', Actual: '{value}'");
}
}
}




In my applications I use my own RestClient that was also on Code Review here. I used it to create a test-client for sending requests that I post for reference purposes.




public interface ITestClient : IRestClient { }

public class TestClient : ITestClient
{
private readonly IRestClient _restClient;

private TestClient(IRestClient restClient)
{
_restClient = restClient;
}

public string BaseUri => _restClient.BaseUri;

public static ITestClient Create(string baseUri, Action<HttpRequestHeaders> configureDefaultRequestHeaders)
{
var restClient = new RestClient(baseUri, configureDefaultRequestHeaders);
return new TestClient(restClient);
}

public Task<T> InvokeAsync<T>(HttpMethodContext context, CancellationToken cancellationToken)
{
return _restClient.InvokeAsync<T>(context, cancellationToken);
}
}





What do you think of this framework? I seems to do its job well but can it do it better? The server is very simple but is this enough or have I missed anything? Convenience and simplicity are the main goals.







c# rest server asp.net-core integration-testing






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 17 hours ago

























asked yesterday









t3chb0t

33.7k746110




33.7k746110












  • Its GitHub home is here.
    – t3chb0t
    yesterday










  • Tell me the difference between Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); }); and Task = host.RunAsync();.
    – Jesse C. Slicer
    yesterday










  • @JesseC.Slicer I thought it runs the Task on a new thread-pool thread but all you can find here is that one shouldn't use it because... it's dangerous, wow, what a wonderful explanation :-| but maybe it's not necessary after all. I'm not entirely sure - that's why I've posted this experiment ;-)
    – t3chb0t
    yesterday












  • I ask because I truly have no idea and I'm dealing with a production issue at the day job that has similar code and stuff is going off into la-la land.
    – Jesse C. Slicer
    yesterday






  • 1




    @JesseC.Slicer this answer recommends using Task.Run for starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
    – t3chb0t
    yesterday




















  • Its GitHub home is here.
    – t3chb0t
    yesterday










  • Tell me the difference between Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); }); and Task = host.RunAsync();.
    – Jesse C. Slicer
    yesterday










  • @JesseC.Slicer I thought it runs the Task on a new thread-pool thread but all you can find here is that one shouldn't use it because... it's dangerous, wow, what a wonderful explanation :-| but maybe it's not necessary after all. I'm not entirely sure - that's why I've posted this experiment ;-)
    – t3chb0t
    yesterday












  • I ask because I truly have no idea and I'm dealing with a production issue at the day job that has similar code and stuff is going off into la-la land.
    – Jesse C. Slicer
    yesterday






  • 1




    @JesseC.Slicer this answer recommends using Task.Run for starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
    – t3chb0t
    yesterday


















Its GitHub home is here.
– t3chb0t
yesterday




Its GitHub home is here.
– t3chb0t
yesterday












Tell me the difference between Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); }); and Task = host.RunAsync();.
– Jesse C. Slicer
yesterday




Tell me the difference between Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); }); and Task = host.RunAsync();.
– Jesse C. Slicer
yesterday












@JesseC.Slicer I thought it runs the Task on a new thread-pool thread but all you can find here is that one shouldn't use it because... it's dangerous, wow, what a wonderful explanation :-| but maybe it's not necessary after all. I'm not entirely sure - that's why I've posted this experiment ;-)
– t3chb0t
yesterday






@JesseC.Slicer I thought it runs the Task on a new thread-pool thread but all you can find here is that one shouldn't use it because... it's dangerous, wow, what a wonderful explanation :-| but maybe it's not necessary after all. I'm not entirely sure - that's why I've posted this experiment ;-)
– t3chb0t
yesterday














I ask because I truly have no idea and I'm dealing with a production issue at the day job that has similar code and stuff is going off into la-la land.
– Jesse C. Slicer
yesterday




I ask because I truly have no idea and I'm dealing with a production issue at the day job that has similar code and stuff is going off into la-la land.
– Jesse C. Slicer
yesterday




1




1




@JesseC.Slicer this answer recommends using Task.Run for starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
– t3chb0t
yesterday






@JesseC.Slicer this answer recommends using Task.Run for starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
– t3chb0t
yesterday

















active

oldest

votes











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209280%2fmaking-sure-application-is-requesting-resources-correctly%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown






























active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209280%2fmaking-sure-application-is-requesting-resources-correctly%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Mont Emei

Province de Neuquén

Journaliste