Ridge by Michal Motyčka
Nuget / site data
Details
Info
Name: Ridge
a html, json and xml parsing library.
Author: Michal Motyčka
NuGet: https://www.nuget.org/packages/Ridge/
You can find more details at https://github.com/Melchy/Ridge
Source : https://github.com/Melchy/Ridge
Original Readme
Ridge
Ridge is a source generator that creates strongly typed HTTP clients for integration tests. HTTP clients generated by Ridge require the
WebApplicationFactory.
The use of the WebApplicationFactory
allows Ridge to access internal components of ASP.NET and analyze them.
This significantly improves route generation and allows implicit support of areas, routing without attributes, and so on.
Ridge supports .NET 6 and newer.
Quick links
Example
// --------------------------------------------ExampleController.cs-------------------------------------------------
[GenerateClient] // Notice the attribute
public class ExamplesController : Controller
{
[HttpGet("ReturnGivenNumber")]
public ActionResult<int> ReturnGivenNumber(
[FromQuery] int input)
{
return input;
}
}
// ------------------------------------------Test.cs----------------------------------------------------------------
[Test]
public async Task CallControllerUsingRidge()
{
using var webApplicationFactory =
new WebApplicationFactory<Program>()
.WithRidge(); // add ridge dependencies to WebApplicationFactory
var client = webApplicationFactory.CreateClient();
// create instance of client generated by source generator
var examplesControllerClient = new ExamplesControllerClient(client, webApplicationFactory.Services);
var response = await examplesControllerClient.ReturnGivenNumber(10);
Assert.True(response.IsSuccessStatusCode);
Assert.AreEqual(10, response.Result);
}
Setup
- Mark controller with the
[GenerateClient]
attribute. This attribute tells the source generator to generate class*YourControllerName*Client
in the assembly which contains the controller. - Call
WithRidge()
extension method onWebApplicationFactory
. - Create instance of
*YourControllerName*Client
. - Create requests using
*YourControllerName*Client
instance.
Hint: Use package
RidgeDotNet.AspNetCore
in yourAspNetCore
project instead ofRidgeDotNet
.RidgeDotNet.AspNetCore
has minimal dependencies, preventing unnecessary test code in your project.
Best practices
- Use
ActionResult<T>
when possible to enable strongly typed response generation. - Use
[FromRoute]
,[FromQuery]
,[FromBody]
, and similar attributes when possible to ensure correct parameter mapping. - Add a logger to check generated requests and responses when necessary. More information here.
- Use
RethrowExceptionInsteadOfReturningHttpResponse
for improved test experience.
Wiki
Full documentation can be found in the wiki.
Features that are not currently supported
Note that you can always fall back to
WebApplicationFactory
when you need to test something that is not supported by Ridge.
- Minimal API
- Custom request types. JSON is the only request type currently supported.
- Single action parameter transformations (add parameter to single action or transform parameter in single action)
[FromForm]
attributes- Actions returning custom implementation of
IActionResult
.
Mappings that are not supported by default
Ridge supports a wide range of parameter mappings, but some special cases are currently not supported by default. Known unsupported mappings are the following:
[FromQuery]
with an array of complex arguments- Complex types with
[FromXXX]
attributes on properties
Example of [FromQuery]
with an array of complex arguments:
public virtual ActionResult NotSupported([FromQuery] IEnumerable<ComplexArgument> complexArguments)
{
//..
}
Example of complex types with [FromXXX]
attributes on properties:
public virtual ActionResult NotSupported(Mixed mixed)
{
//..
}
public class Mixed
{
[FromBody]
public string BodyName { get; set; }
[FromHeader]
public string HeaderName { get; set; }
}
If you need to use this feature then consider writing
custom HttpRequestFactoryMiddleware
or creating an issue.
Contributions
Icon made by Freepik from www.flaticon.com.
About
Generating test classes for controllers
How to use
Example ( source csproj, source files )
- CSharp Project
- Program.cs
- WeatherForecastController.cs
- BasicTests.cs
This is the CSharp Project that references Ridge
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" />
<PackageReference Include="RidgeDotNet.AspNetCore" Version="2.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of Ridge in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
public partial class Program { }
This is the use of Ridge in WeatherForecastController.cs
using Microsoft.AspNetCore.Mvc;
using Ridge.AspNetCore.GeneratorAttributes;
namespace RidgeDemoWebApp.Controllers
{
[GenerateClient]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
This is the use of Ridge in BasicTests.cs
using Microsoft.AspNetCore.Mvc.Testing;
using RidgeDemoWebApp.Controllers;
using Xunit;
namespace TestRidgeApp;
[TestClass]
public class BasicTests
{
[TestMethod]
public async Task CallControllerUsingRidge()
{
using var webApplicationFactory =
new WebApplicationFactory<Program>()
.WithRidge(); // add ridge dependencies to WebApplicationFactory
var client = webApplicationFactory.CreateClient();
// create instance of client generated by source generator
var examplesControllerClient = new WeatherForecastControllerClient(client, webApplicationFactory.Services);
var response = await examplesControllerClient.Get();
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.AreEqual(5, response.Result.Count());
}
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- WeatherForecastController_Client.g.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by the Ridge source generator
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
#pragma warning disable CS0419
using Ridge.AspNetCore;
using Ridge.AspNetCore.Serialization;
using Ridge.AspNetCore.Response;
using Ridge.AspNetCore.Parameters;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace RidgeDemoWebApp.Controllers
{
/// <summary>
/// Generated Api client. Calls <see cref="RidgeDemoWebApp.Controllers.WeatherForecastController" />
/// </summary>
public class WeatherForecastControllerClient
{
private readonly IApplicationClient _applicationClient;
/// <summary>
/// Creates client for controller.
/// </summary>
/// <param name="httpClient">
/// HttpClient which will be used to call application.
/// </param>
/// <param name="serviceProvider">
/// Application serviceProvider.
/// </param>
public WeatherForecastControllerClient(HttpClient httpClient, IServiceProvider serviceProvider)
{
var applicationClientFactory = serviceProvider.GetService<IApplicationClientFactory>();
if(applicationClientFactory == null)
{
throw new InvalidOperationException("'IApplicationClientFactory' could not be resolved. Did you forget to call WithRidge()?.");
}
else
{
_applicationClient = applicationClientFactory.CreateClient(serviceProvider, httpClient);
}
}
/// <summary>
/// Calls <see cref="RidgeDemoWebApp.Controllers.WeatherForecastController.Get" />.
/// </summary>
public async Task<HttpCallResponse<System.Collections.Generic.IEnumerable<RidgeDemoWebApp.WeatherForecast>>> Get(params AdditionalParameter[] additionalParameters)
{
var methodName = nameof(RidgeDemoWebApp.Controllers.WeatherForecastController.Get);
var actionParameters = new Type[] {
};
var parametersAndTransformations = new List<RawParameterAndTransformationInfo>()
{
};
return await _applicationClient.CallAction<System.Collections.Generic.IEnumerable<RidgeDemoWebApp.WeatherForecast>,RidgeDemoWebApp.Controllers.WeatherForecastController>(methodName, actionParameters, additionalParameters, parametersAndTransformations);
}
}
}
#pragma warning restore CS0419
#nullable restore
Usefull
Download Example (.NET C# )
Share Ridge
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Ridge