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.
c# rest server asp.net-core integration-testing
add a comment |
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.
c# rest server asp.net-core integration-testing
Its GitHub home is here.
– t3chb0t
yesterday
Tell me the difference betweenTask = Task.Factory.StartNew(async () => { await _host.RunAsync(); });andTask = host.RunAsync();.
– Jesse C. Slicer
yesterday
@JesseC.Slicer I thought it runs theTaskon 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 usingTask.Runfor starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
– t3chb0t
yesterday
add a comment |
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.
c# rest server asp.net-core integration-testing
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
c# rest server asp.net-core integration-testing
edited 17 hours ago
asked yesterday
t3chb0t
33.7k746110
33.7k746110
Its GitHub home is here.
– t3chb0t
yesterday
Tell me the difference betweenTask = Task.Factory.StartNew(async () => { await _host.RunAsync(); });andTask = host.RunAsync();.
– Jesse C. Slicer
yesterday
@JesseC.Slicer I thought it runs theTaskon 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 usingTask.Runfor starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]
– t3chb0t
yesterday
add a comment |
Its GitHub home is here.
– t3chb0t
yesterday
Tell me the difference betweenTask = Task.Factory.StartNew(async () => { await _host.RunAsync(); });andTask = host.RunAsync();.
– Jesse C. Slicer
yesterday
@JesseC.Slicer I thought it runs theTaskon 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 usingTask.Runfor 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
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
Its GitHub home is here.
– t3chb0t
yesterday
Tell me the difference between
Task = Task.Factory.StartNew(async () => { await _host.RunAsync(); });andTask = host.RunAsync();.– Jesse C. Slicer
yesterday
@JesseC.Slicer I thought it runs the
Taskon 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.Runfor starting new threads. It looks like I (we) should change it. Thanks for asking - I think I'm smarter now ;-]– t3chb0t
yesterday