Testing with xUnit.net
xUnit.net is one of the most popular .NET test frameworks. It is part of the xUnit family of test frameworks, and it is a popular alternative to NUnit. Boa Constrictor works seamlessly with xUnit.net. Boa Constrictor’s fluent-like syntax makes xUnit.net test classes easy to read and understand.
This user guide shows how to integrate Boa Constrictor with xUnit.net. The test case in the example code below will follow the same steps as the test case from the tutorial: a Web UI test for Wikipedia searches.
xUnit.net Test Projects
Boa Constrictor can integrate with any xUnit.net test project. If you are new to xUnit.net, read xUnit.net’s official documentation to learn how to get started with xUnit.net. Steps to set up an xUnit.net test project include:
- Create a new xUnit.net test project in Visual Studio.
- Install the Boa.Constrictor NuGet packages into the project.
.NET Core projects are typically recommended over .NET Framework projects. The project will need the xunit and xunit.runner.visualstudio NuGet packages, but Visual Studio should automatically add them when creating an xUnit.net project. You may need to add other NuGet packages like FluentAssertions as well.
xUnit.net Test Classes
xUnit.net test classes should have all the appropriate using
statements for the namespaces they need.
They should also have an instance variable for the Screenplay Actor object.
Below is a class stub for the example test class named WikipediaTest
:
using Boa.Constrictor.Screenplay;
using Boa.Constrictor.Selenium;
using Boa.Constrictor.Xunit;
using OpenQA.Selenium.Chrome;
using Xunit;
namespace Boa.Constrictor.Example
{
public class WikipediaTest
{
private IActor Actor { get; set; }
// ...
}
Screenplay Setup
xUnit.net test classes do not have “set up” methods like
NUnit’s [SetUp]
methods.
Instead, test case setup is done in test class constructors.
Each test should have its own Actor with its own set of Abilities to preserve test case independence.
xUnit also does not capture console output. Instead, it offers ITestOutputHelper.
In order to configure an actor to use this mechanism you may use TestOutputLogger
Below is the WikipediaTest
constructor that
constructs an Actor and gives it the Ability to browse the web with ChromeDriver:
public WikipediaTest(ITestOutputHelper output)
{
Actor = new Actor(name: "Andy", logger: new TestOutputLogger(output));
Actor.Can(BrowseTheWeb.With(new ChromeDriver()));
}
Screenplay Test Case
In xUnit.net test classes, methods with the [Fact]
or [Theory]
attributes are individual test cases.
With Boa Constrictor, most test cases should be a series of Screenplay interactions, like
Actor.AttemptsTo(...)
, Actor.AsksFor(...)
, and Actor.WaitsUntil(...)
.
Below is a test case method for the example WikipediaTest
class.
The specific pages (MainPage
and ArticlePage
) and custom interactions (SearchWikipedia
)
come from the Boa.Constrictor.Example
project:
[Fact]
public void TestWikipediaSearch()
{
Actor.AttemptsTo(Navigate.ToUrl(MainPage.Url));
Assert.Equal("", Actor.AskingFor(ValueAttribute.Of(MainPage.SearchInput)));
Actor.AttemptsTo(SearchWikipedia.For("Giant panda"));
Actor.WaitsUntil(Text.Of(ArticlePage.Title), IsEqualTo.Value("Giant panda"));
}
xUnit.net provides an Assert
class for built-in assertions, which is used in the test method above.
You can use FluentAssertions instead for more readable Screenplay calls:
Actor.AskingFor(ValueAttribute.Of(MainPage.SearchInput)).Should().BeEmpty();
Screenplay Cleanup
Cleanup procedures are not required for all types of tests, but they are required for WebDriver-based tests.
xUnit.net test classes must implement the System.IDisposable
interface to handle test case cleanup:
public class WikipediaTest : System.IDisposable
The Dispose
method performs cleanup operations after each test.
The code below safely quits the browser for Web UI test cleanup:
public void Dispose()
{
Actor.AttemptsTo(QuitWebDriver.ForBrowser());
}
A Complete xUnit.net Test Class
The complete code for WikipediaTest
is below:
using Boa.Constrictor.Screenplay;
using Boa.Constrictor.Selenium;
using OpenQA.Selenium.Chrome;
using Xunit;
namespace Boa.Constrictor.Example
{
public class WikipediaTest : System.IDisposable
{
private IActor Actor { get; set; }
public WikipediaTest(ITestOutputHelper output)
{
Actor = new Actor(name: "Andy", logger: new TestOutputLogger(output));
Actor.Can(BrowseTheWeb.With(new ChromeDriver()));
}
public void Dispose()
{
Actor.AttemptsTo(QuitWebDriver.ForBrowser());
}
[Fact]
public void TestWikipediaSearch()
{
Actor.AttemptsTo(Navigate.ToUrl(MainPage.Url));
Assert.Equal("", Actor.AskingFor(ValueAttribute.Of(MainPage.SearchInput)));
Actor.AttemptsTo(SearchWikipedia.For("Giant panda"));
Actor.WaitsUntil(Text.Of(ArticlePage.Title), IsEqualTo.Value("Giant panda"));
}
}
}
Shared Context
xUnit offers a number of ways to share Setup and Cleanup code
For example, a Class Fixture could be used to perform a
setup that occurs only once for
all the tests in a class (Similar to NUnits [OneTimeSetup]
` method). Instead, one time setup
is done in the class fixture constructor. It’s not advised to have a public Actor in a shared context.
Each test should have its own Actor with its own set of Abilities to preserve test case independence.
However, you could use a private Actor to perform any screenplay task you wish in your one-time setup.
xUnit does not capture console output, or make use of its ITestOutputHelper
in any of it’s extensibility objects.
Instead, it offers IMessageSink. In order to configure
an actor to use this mechanism you may use MessageSinkLogger
Below is an example of a possible Test Fixture usage. It connects and initializes some database data.
xUnit injects IMessageSink
which can be used to initialize MessageSinkLogger
. The connection is a public property
so that it can be shared to the test case
public class DatabaseFixture : IDisposable
{
public DatabaseFixture(IMessageSink messageSink)
{
Connection = new SqlConnection("MyConnectionString");
var actor = new Actor("Fixture Actor", new MessageSinkLogger(messageSink));
actor.Can(ConnectToDatabase.Using(Connection));
// ... initialize data in the test database ...
}
public void Dispose()
{
// ... clean up test data from the database ...
}
public SqlConnection Connection{ get; private set; }
}
In the test case, it implements the IClassFixture interface which allows the fixture to be injected into the constructor. From here we can extract the database connection, and initialize our actor as we would for any other test.
public class DatabaseTest : IClassFixture<DatabaseFixture>
{
public Actor DatabaseAdmin { get; set; }
public DatabaseTest(DatabaseFixture databaseFixture, ITestOutputHelper outputHelper)
{
DatabaseAdmin = new Actor("Admin", new TestOutputLogger(outputHelper));
DatabaseAdmin.Can(ConnectToDatabase.Using(databaseFixture.Connection));
}
[Fact]
public void CanDeleteWidget()
{
/* Use DatabaseAdmin to get access to database connection */
}
}
IMessageSink
does not write to the test output. Instead, it writes to the test runner diagnostics.
This is a limitation of xUnit and cannot be controlled by Boa.Constrictor. Make sure you have the diagnosticMessages flag
enabled. Where you find this info will depend on your IDE. In Visual Studio, it’s available in the output window.