C# Automated Testing with xUnit.net
xUnit.net is a free, open-source, community-focused unit testing framework for .NET. It is the successor to NUnit and was written by the original inventor of NUnit v2.
xUnit.net is widely used in the .NET ecosystem and offers modern features like:
- Built-in support for parallel test execution
- Theory tests with
[InlineData]for data-driven testing - Constructor/Dispose pattern for test setup and teardown
- Fixtures for sharing context between tests
Installation
Create a new xUnit test project and add the required packages:
# Create a new xUnit test project
dotnet new xunit -n SeleniumTests
cd SeleniumTests
# Add Selenium WebDriver packages
dotnet add package Selenium.WebDriver
dotnet add package Selenium.Support
Or add these packages to your .csproj file:
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
<PackageReference Include="Selenium.WebDriver" Version="4.18.1" />
<PackageReference Include="Selenium.Support" Version="4.18.1" />
</ItemGroup>
Requirements: xUnit 2.x requires .NET 6.0 or higher. The examples use modern C# features like nullable reference types.
Your first xUnit test
The example below shows a complete xUnit test that connects to TestingBot and runs a simple browser test.
xUnit uses constructor/destructor (or IDisposable) for setup and teardown instead of attributes.
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
using Xunit;
namespace SeleniumTests;
public class GoogleTest : IDisposable
{
private readonly IWebDriver _driver;
private readonly string _sessionId;
public GoogleTest()
{
// Configure Chrome options
var options = new ChromeOptions();
options.BrowserVersion = "latest";
options.PlatformName = "WIN10";
options.AddAdditionalOption("tb:options", new Dictionary<string, object>
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = "xUnit Google Test"
});
// Connect to TestingBot
_driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options
);
_sessionId = ((RemoteWebDriver)_driver).SessionId.ToString();
}
[Fact]
public void GoogleTitle_ShouldContainGoogle()
{
_driver.Navigate().GoToUrl("https://www.google.com");
Assert.Contains("Google", _driver.Title);
}
public void Dispose()
{
if (_driver != null)
{
// Mark test as passed (simplified - see API section for proper implementation)
((IJavaScriptExecutor)_driver).ExecuteScript("tb:test-result=passed");
_driver.Dispose();
}
}
}
Run your test with:
TB_KEY=your_key TB_SECRET=your_secret dotnet test
Data-Driven Testing with Theory
xUnit's [Theory] attribute allows you to run the same test with different data.
This is perfect for cross-browser testing:
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Remote;
using Xunit;
namespace SeleniumTests;
public class CrossBrowserTest : IDisposable
{
private IWebDriver? _driver;
[Theory]
[InlineData("chrome", "latest", "WIN10")]
[InlineData("firefox", "latest", "WIN10")]
[InlineData("MicrosoftEdge", "latest", "WIN10")]
[InlineData("safari", "latest", "SONOMA")]
public void HomePage_ShouldLoad_OnAllBrowsers(string browser, string version, string platform)
{
// Create browser-specific options
DriverOptions options = browser.ToLower() switch
{
"chrome" => new ChromeOptions(),
"firefox" => new FirefoxOptions(),
"microsoftedge" => new EdgeOptions(),
"safari" => new SafariOptions(),
_ => new ChromeOptions()
};
options.BrowserVersion = version;
options.PlatformName = platform;
options.AddAdditionalOption("tb:options", new Dictionary<string, object>
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = $"Cross-browser test - {browser}"
});
_driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options
);
_driver.Navigate().GoToUrl("https://testingbot.com");
Assert.Contains("TestingBot", _driver.Title);
}
public void Dispose()
{
if (_driver != null)
{
((IJavaScriptExecutor)_driver).ExecuteScript("tb:test-result=passed");
_driver.Dispose();
}
}
}
Specify Browsers & Devices
To run tests on specific browsers and platforms, configure the appropriate DriverOptions class.
// Chrome on Windows
var chromeOptions = new ChromeOptions();
chromeOptions.BrowserVersion = "latest";
chromeOptions.PlatformName = "WIN10";
chromeOptions.AddAdditionalOption("tb:options", new Dictionary<string, object>
{
["key"] = "YOUR_TB_KEY",
["secret"] = "YOUR_TB_SECRET"
});
var driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions
);
To see how to configure different browsers and platforms, use the picker below:
Testing Internal Websites
Use TestingBot Tunnel to securely test internal or staged websites that aren't publicly accessible.
1. Download the tunnel and start it:
java -jar testingbot-tunnel.jar key secret
2. Update your test to connect through the tunnel (change the hub URL to localhost:4445):
// Connect through the tunnel instead of directly to TestingBot
var driver = new RemoteWebDriver(
new Uri("http://localhost:4445/wd/hub"),
options
);
// Now you can access internal URLs
driver.Navigate().GoToUrl("http://internal.yourcompany.com");
Run tests in Parallel
xUnit runs tests in parallel by default. Tests in different test classes run in parallel, while tests in the same class run sequentially.
Configure Parallelism
Control parallel execution by adding an xunit.runner.json file to your project:
{
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": 5
}
Make sure to include it in your .csproj:
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Using Test Collections
Group tests that shouldn't run in parallel using [Collection]:
// Tests in the same collection run sequentially
[Collection("Sequential")]
public class SequentialTests1 { }
[Collection("Sequential")]
public class SequentialTests2 { }
// Tests without a collection run in parallel with other classes
Queuing
Every TestingBot plan has a limit on parallel tests. If you exceed this limit, additional tests are queued (up to 6 minutes) and run as slots become available.
Mark tests as passed/failed
To report test results to TestingBot, use JavaScript execution to set the test status:
public class TestBase : IDisposable
{
protected IWebDriver Driver { get; }
private bool _testPassed = true;
public TestBase()
{
var options = new ChromeOptions();
options.BrowserVersion = "latest";
options.PlatformName = "WIN10";
options.AddAdditionalOption("tb:options", new Dictionary<string, object>
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? ""
});
Driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options
);
}
protected void MarkTestFailed()
{
_testPassed = false;
}
public void Dispose()
{
if (Driver != null)
{
var status = _testPassed ? "passed" : "failed";
((IJavaScriptExecutor)Driver).ExecuteScript($"tb:test-result={status}");
Driver.Dispose();
}
}
}
// Usage in test class
public class MyTests : TestBase
{
[Fact]
public void TestSomething()
{
try
{
Driver.Navigate().GoToUrl("https://example.com");
Assert.Equal("Example Domain", Driver.Title);
}
catch
{
MarkTestFailed();
throw;
}
}
}
Other C# Framework examples
-
NUnit
An unit testing framework that is open source written in C#.
-
PNunit
With PNUnit you can run several tests in parallel.
-
SpecFlow
SpecFlow allows you to run Automated .NET tests using Cucumber-compatible Gherkin syntax.
-
MSTest
MSTest framework is a test framework which is included, by default, with Microsoft Visual Studio.
-
Reqnroll
Reqnroll is the successor to SpecFlow for BDD testing with Gherkin syntax.