[ad_1]
I have been doing not simply Unit Testing for my websites however full on Integration Testing and Browser Automation Testing as early as 2007 with Selenium. Recently, nevertheless, I have been utilizing the quicker and usually extra appropriate Playwright. It has one API and may check on Home windows, Linux, Mac, regionally, in a container (headless), in my CI/CD pipeline, on Azure DevOps, or in GitHub Actions.
For me, it is that final second of fact to ensure that the positioning runs utterly from finish to finish.
I can write these Playwright exams in one thing like TypeScript, and I may launch them with node, however I like operating finish unit exams and utilizing that check runner and check harness as my leaping off level for my .NET functions. I am used to proper clicking and “run unit exams” and even higher, proper click on and “debug unit exams” in Visible Studio or VS Code. This will get me the advantage of the entire assertions of a full unit testing framework, and all the advantages of utilizing one thing like Playwright to automate my browser.
In 2018 I used to be utilizing WebApplicationFactory and a few tough hacks to mainly spin up ASP.NET inside .NET (on the time) Core 2.1 inside the unit exams after which launching Selenium. This was type of janky and would require to manually begin a separate course of and handle its life cycle. Nonetheless, I stored on with this hack for quite a lot of years mainly making an attempt to get the Kestrel Net Server to spin up inside my unit exams.
I’ve lately upgraded my primary web site and podcast web site to .NET 8. Understand that I have been transferring my web sites ahead from early early variations of .NET to the newest variations. The weblog is fortunately operating on Linux in a container on .NET 8, however its authentic code began in 2002 on .NET 1.1.
Now that I am on .NET 8, I scandalously found (as my unit exams stopped working) that the remainder of the world had moved from IWebHostBuilder to IHostBuilder 5 model of .NET in the past. Gulp. Say what you’ll, however the backward compatibility is spectacular.
As such my code for Program.cs modified from this
public static void Predominant(string[] args)
{
CreateWebHostBuilder(args).Construct().Run();
}public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
to this:
public static void Predominant(string[] args)
{
CreateHostBuilder(args).Construct().Run();
}public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).
ConfigureWebHostDefaults(WebHostBuilder => WebHostBuilder.UseStartup<Startup>());
Not a serious change on the skin however tidies issues up on the within and units me up with a extra versatile generic host for my net app.
My unit exams stopped working as a result of my Kestral Net Server hack was now not firing up my server.
Right here is an instance of my purpose from a Playwright perspective inside a .NET NUnit check.
[Test]
public async Job DoesSearchWork()
{
await Web page.GotoAsync(Url);await Web page.Locator("#topbar").GetByRole(AriaRole.Hyperlink, new() { Identify = "episodes" }).ClickAsync();
await Web page.GetByPlaceholder("search and filter").ClickAsync();
await Web page.GetByPlaceholder("search and filter").TypeAsync("spouse");
const string visibleCards = ".showCard:seen";
var ready = await Web page.WaitForSelectorAsync(visibleCards, new PageWaitForSelectorOptions() { Timeout = 500 });
await Count on(Web page.Locator(visibleCards).First).ToBeVisibleAsync();
await Count on(Web page.Locator(visibleCards)).ToHaveCountAsync(5);
}
I like this. Good and clear. Definitely right here we’re assuming that we have now a URL in that first line, which shall be localhost one thing, after which we assume that our net software has began up by itself.
Right here is the setup code that begins my new “net software check builder manufacturing unit,” yeah, the title is silly but it surely’s descriptive. Be aware the OneTimeSetUp and the OneTimeTearDown. This begins my net app inside the context of my TestHost. Be aware the :0 makes the app discover a port which I then, sadly, need to dig out and put into the Url personal to be used inside my Unit Checks. Be aware that the <Startup> is actually my Startup class inside Startup.cs which hosts my app’s pipeline and Configure and ConfigureServices get setup right here so routing all works.
personal string Url;
personal WebApplication? _app = null;[OneTimeSetUp]
public void Setup()
{
var builder = WebApplicationTestBuilderFactory.CreateBuilder<Startup>();var startup = new Startup(builder.Surroundings);
builder.WebHost.ConfigureKestrel(o => o.Hear(IPAddress.Loopback, 0));
startup.ConfigureServices(builder.Companies);
_app = builder.Construct();// hear on any native port (therefore the 0)
startup.Configure(_app, _app.Configuration);
_app.Begin();//you might be kidding me
Url = _app.Companies.GetRequiredService<IServer>().Options.GetRequiredFeature<IServerAddressesFeature>().Addresses.Final();
}[OneTimeTearDown]
public async Job TearDown()
{
await _app.DisposeAsync();
}
So what horrors are buried in WebApplicationTestBuilderFactory? The primary bit is unhealthy and we must always repair it for .NET 9. The remaining is definitely each good, with a hat tip to David Fowler for his assist and steerage! That is the magic and the ick in a single small helper class.
public class WebApplicationTestBuilderFactory
{
public static WebApplicationBuilder CreateBuilder<T>() the place T : class
{
//This ungodly code requires an unused reference to the MvcTesting bundle that hooks up
// MSBuild to create the manifest file that's learn right here.
var testLocation = Path.Mix(AppContext.BaseDirectory, "MvcTestingAppManifest.json");
var json = JsonObject.Parse(File.ReadAllText(testLocation));
var asmFullName = typeof(T).Meeting.FullName ?? throw new InvalidOperationException("Meeting Full Identify is null");
var contentRootPath = json?[asmFullName]?.GetValue<string>();//spin up an actual dwell net software inside TestHost.exe
var builder = WebApplication.CreateBuilder(
new WebApplicationOptions()
{
ContentRootPath = contentRootPath,
ApplicationName = asmFullName
});
return builder;
}
}
The primary 4 traces are nasty. As a result of the check runs within the context of a distinct listing and my web site must run inside the context of its personal content material root path, I’ve to pressure the content material root path to be appropriate and the one approach to try this is by getting the apps base listing from a file generated inside MSBuild from the (ageing) MvcTesting bundle. The bundle isn’t used, however by referencing it it will get into the construct and makes that file that I then use to tug out the listing.
If we are able to eliminate that “hack” and pull the listing from context elsewhere, then this helper perform turns right into a single line and .NET 9 will get WAY WAY extra testable!
Now I can run my Unit Checks AND Playwright Browser Integration Checks throughout all OS’s, headed or headless, in docker or on the steel. The positioning is up to date to .NET 8 and all is correct with my code. Effectively, it runs not less than. 😉
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, marketing consultant, father, diabetic, and Microsoft worker. He’s a failed stand-up comedian, a cornrower, and a e book creator.
[ad_2]