Skip to main content

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.

GoogleTest.cs:
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.

Was this page helpful?

Looking for More Help?

Have questions or need more information?
You can reach us via the following channels: