Nice3point.TUnit.Revit
2027.0.8
Prefix Reserved
See the version list below for details.
dotnet add package Nice3point.TUnit.Revit --version 2027.0.8
NuGet\Install-Package Nice3point.TUnit.Revit -Version 2027.0.8
<PackageReference Include="Nice3point.TUnit.Revit" Version="2027.0.8" />
<PackageVersion Include="Nice3point.TUnit.Revit" Version="2027.0.8" />
<PackageReference Include="Nice3point.TUnit.Revit" />
paket add Nice3point.TUnit.Revit --version 2027.0.8
#r "nuget: Nice3point.TUnit.Revit, 2027.0.8"
#:package Nice3point.TUnit.Revit@2027.0.8
#addin nuget:?package=Nice3point.TUnit.Revit&version=2027.0.8
#tool nuget:?package=Nice3point.TUnit.Revit&version=2027.0.8
Testing Framework for Revit
Write unit tests for your Revit add-ins using the TUnit testing framework with source-generated tests, parallel execution, and Microsoft.Testing.Platform support.
Installation
You can install this library as a NuGet package.
The packages are compiled for specific versions of Revit. To support different versions of libraries in one project, use the RevitVersion property:
<PackageReference Include="Nice3point.TUnit.Revit" Version="$(RevitVersion).*"/>
Writing your first test
Start by creating a new class inheriting from RevitApiTest:
public class MyTestClass : RevitApiTest
{
}
Add a method with the [Test] attribute:
public class MyTestClass : RevitApiTest
{
[Test]
public async Task MyTest()
{
}
}
This is your runnable test. RevitApiTest runs every test and hook of the class within Revit's single-threaded API context.
Running your tests
TUnit is built on top of the Microsoft.Testing.Platform. Combined with source-generated tests, running your tests is available in multiple ways.
dotnet run
For simple project execution, dotnet run is the preferred method, allowing easier command line flag passing.
cd 'C:/Your/Test/Directory'
dotnet run -c "Release.R26"
dotnet test
dotnet test requires the configuration to target the desired Revit version.
cd 'C:/Your/Test/Directory'
dotnet test -c "Release.R26"
You must have a licensed copy of Autodesk Revit installed on your machine to run tests, with a version that matches the selected Solution configuration.
JetBrains Rider
The Enable Testing Platform support option must be selected in Settings > Build, Execution, Deployment > Unit Testing > Testing Platform.
Application testing
Test Revit application-level functionality using the Application property exposed by RevitApiTest:
public sealed class ApplicationTests : RevitApiTest
{
[Test]
public async Task Cities_BuiltinSet_IsNotEmpty()
{
var cities = Application.Cities.Cast<City>();
await Assert.That(cities).IsNotEmpty();
}
[Test]
public async Task Create_XYZ_ValidDistance()
{
var point = Application.Create.NewXYZ(3, 4, 5);
await Assert.That(point.DistanceTo(XYZ.Zero)).IsEqualTo(7).Within(0.1);
}
}
Long-running operations use the test's CancellationToken for cooperative cancellation.
Cleanup hooks and subsequent tests wait for the running test body to finish.
Document testing
Tests that pass alone but fail together are a classic sign of shared state. Give each test its own document — created in [Before(Test)], closed in [After(Test)] — and that problem disappears entirely.
Use the setup hook to seed the document with exactly the state each test needs. Feel free to use [Before(Class)] hook for read-only tests.
public sealed class ModelSeedTests : RevitApiTest
{
private Document _document = null!;
private IList<Wall> _exteriorWalls = null!;
[Before(Test)]
public void SeedModel()
{
_document = Application.NewProjectDocument(UnitSystem.Metric);
using var transaction = new Transaction(_document, "Seed model");
transaction.Start();
_exteriorWalls =
[
Wall.Create(_document, Line.CreateBound(new XYZ(0, 0, 0), new XYZ(10, 0, 0)), level.Id, false),
Wall.Create(_document, Line.CreateBound(new XYZ(10, 0, 0), new XYZ(10, 6, 0)), level.Id, false),
Wall.Create(_document, Line.CreateBound(new XYZ(10, 6, 0), new XYZ(0, 6, 0)), level.Id, false),
Wall.Create(_document, Line.CreateBound(new XYZ(0, 6, 0), new XYZ(0, 0, 0)), level.Id, false),
];
transaction.Commit();
}
[After(Test)]
public void CloseModel()
{
_document.Close(false);
}
[Test]
public async Task FilteredElementCollector_ExteriorWalls_MatchSeededCount()
{
var walls = new FilteredElementCollector(_document)
.WhereElementIsNotElementType()
.OfClass(typeof(Wall))
.ToList();
await Assert.That(walls.Count).IsEqualTo(_exteriorWalls.Count);
}
[Test]
public async Task Transaction_DemolishWall_RemainingWallCountDecreases()
{
var targetId = _exteriorWalls[0].Id;
using var transaction = new Transaction(_document, "Demolish wall");
transaction.Start();
_document.Delete(targetId);
transaction.Commit();
var remainingWalls = new FilteredElementCollector(_document)
.WhereElementIsNotElementType()
.OfClass(typeof(Wall))
.ToElementIds();
await Assert.That(remainingWalls.Count).IsEqualTo(_exteriorWalls.Count - 1);
}
}
User interface testing
Test the functionality that needs the Revit user interface by deriving from RevitApiUiTest.
The class exposes UiApplication, and runs every test body and hook on the Revit thread inside a Revit API context:
public sealed class UiDocumentTests : RevitApiUiTest
{
private UIDocument _uiDocument = null!;
[Before(Test)]
public void OpenModel()
{
_uiDocument = UiApplication.OpenAndActivateDocument(modelPath);
}
[Test]
public async Task SetElementIds_ActiveDocument_SelectsTheLevels()
{
var levelIds = _uiDocument.Document.CollectElements()
.OfClass<Level>()
.ToElementIds();
_uiDocument.Selection.SetElementIds(levelIds);
await Assert.That(_uiDocument.Selection.GetElementIds()).IsEquivalentTo(levelIds);
}
[Test]
public async Task GetOpenUIViews_ActiveDocument_ContainsTheActiveView()
{
var openViewIds = _uiDocument.GetOpenUIViews()
.Select(uiView => uiView.ViewId)
.ToList();
await Assert.That(openViewIds).Contains(_uiDocument.ActiveView.Id);
}
[Test]
public async Task CanPostCommand_Default3DView_IsPostable()
{
var commandId = RevitCommandId.LookupPostableCommandId(PostableCommand.Default3DView);
await Assert.That(UiApplication.CanPostCommand(commandId)).IsTrue();
}
}
UI tests run one at a time, in parallel with RevitApiTest tests.
To run them in one queue with RevitApiTest tests, mark the class with [ParallelLimiter<RevitParallelLimit>]:
[ParallelLimiter<RevitParallelLimit>]
public sealed class UiDocumentTests : RevitApiUiTest
{
}
[Timeout] applies to each test body attempt.
Setup and cleanup hooks run outside that timeout.
Long-running operations use the test's CancellationToken for cooperative cancellation.
Cleanup hooks and the next UI test wait for the running body to finish.
The examples demonstrate basic testing functionality. This library only adds support for working within the Revit API context. For comprehensive documentation on assertions, attributes, test configuration, and advanced features, please refer to the official TUnit documentation.
More examples, including parametrized model and family tests, are available in the test project.
Test configuration
Revit Environment
TUnit initializes Revit with the English - United States language, the C:\Program Files\Autodesk\Revit {version} installation path, and an add-in with a random ID. To override these defaults:
Add the assembly-level attributes to any .cs file in your project (e.g., TestsConfiguration.cs):
using Nice3point.Revit.Injector.Attributes; [assembly: RevitLanguage("ENU")] [assembly: RevitInstallationPath(@"D:\Autodesk\Revit Preview")] [assembly: RevitClient(Id = "B3E7F5A2-9C4D-4E81-A6F0-2D8C51E7B934", Name = "Tests", VendorId = "Contoso")]Or add the attributes directly to your .csproj file:
<ItemGroup> <AssemblyAttribute Include="Nice3point.Revit.Injector.Attributes.RevitLanguageAttribute"> <_Parameter1>ENU</_Parameter1> </AssemblyAttribute> <AssemblyAttribute Include="Nice3point.Revit.Injector.Attributes.RevitInstallationPathAttribute"> <_Parameter1>D:\Autodesk\Revit $(RevitVersion)</_Parameter1> </AssemblyAttribute> <AssemblyAttribute Include="Nice3point.Revit.Injector.Attributes.RevitClientAttribute"> <Id>B3E7F5A2-9C4D-4E81-A6F0-2D8C51E7B934</Id> <Name>Tests</Name> <VendorId>Contoso</VendorId> </AssemblyAttribute> </ItemGroup>
The RevitLanguage attribute accepts a language name (e.g., "English - United States"), code (e.g., "ENU")
or LanguageType enum value (e.g., "English_GB" or "15").
The RevitClient attribute sets the add-in that Revit attributes every API call of the tests to, and ActiveAddInId of Application and UiApplication returns it.
Each property is optional, Id accepts any format Guid accepts, VendorId must match the vendor of an Extensible Storage schema the tests write with vendor access.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- PolyHook2.NET (>= 1.1.9)
- TUnit (>= 1.76.45)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (5)
Showing the top 5 popular GitHub repositories that depend on Nice3point.TUnit.Revit:
| Repository | Stars |
|---|---|
|
lookup-foundation/RevitLookup
Interactive Revit database exploration tool
|
|
|
Nice3point/RevitTemplates
Project templates for Revit add-ins
|
|
|
mcp-servers-for-revit/mcp-servers-for-revit
🔥💧🤖 Sparx fork of the mcp-servers-for-revit/revit-mcp project
|
|
|
Nice3point/RevitToolkit
Toolkit for Revit plugin development
|
|
|
Nice3point/RevitExtensions
Extensions for Revit plugin development
|
| Version | Downloads | Last Updated |
|---|---|---|
| 2027.0.9 | 0 | 11/2/2026 |
| 2027.0.8 | 352 | 10/29/2026 |
| 2027.0.7 | 1,433 | 9/29/2026 |
| 2026.1.9 | 0 | 11/2/2026 |
| 2026.1.8 | 346 | 10/29/2026 |
| 2026.1.7 | 808 | 9/29/2026 |
| 2025.1.9 | 0 | 11/2/2026 |
| 2025.1.8 | 367 | 10/29/2026 |
| 2025.1.7 | 871 | 9/29/2026 |
| 2024.1.9 | 0 | 11/2/2026 |
| 2024.1.8 | 295 | 10/29/2026 |
| 2024.1.7 | 336 | 9/29/2026 |
| 2023.1.9 | 0 | 11/2/2026 |
| 2023.1.8 | 99 | 10/29/2026 |
| 2023.1.7 | 292 | 9/29/2026 |
| 2022.1.9 | 0 | 11/2/2026 |
| 2022.1.8 | 66 | 10/29/2026 |
| 2022.1.7 | 204 | 9/29/2026 |
| 2021.1.9 | 0 | 11/2/2026 |
| 2021.1.8 | 64 | 10/29/2026 |
Revit client configuration
Tests run as the Revit add-in the RevitClient assembly attribute declares https://github.com/Nice3point/RevitUnit/issues/129:
using Nice3point.Revit.Injector.Attributes;
[assembly: RevitClient(Id = B3E7F5A2-9C4D-4E81-A6F0-2D8C51E7B934, Name = Tests, VendorId = Contoso)]
Application.ActiveAddInId in RevitApiTest and UiApplication.ActiveAddInId in RevitApiUiTest return the declared add-in.
Each property is optional: Id defaults to a random GUID, Name to the ID in uppercase, and VendorId to Autodesk.
UI test order
The UI tests of a class run one after another https://github.com/Nice3point/RevitUnit/issues/132:
• [Before(Class)] of a RevitApiUiTest class runs right before the first test of the class.
• [After(Class)] runs right after the last test of the class.
A document that a class opens in [Before(Class)] and closes in [After(Class)] is open only during the tests of that class.
• The timeout of a [Before(Class)] hook excludes the wait for the tests of other classes.
• The duration of the first test of a class includes its [Before(Class)] hooks.
• A UI test that waits for its turn occupies no parallel slot.
Bug fixes
• Fixed [Before(Class)] and [After(Class)] hooks of RevitApiTest and RevitApiUiTest classes running outside the Revit thread on Revit 2021 to 2024.
• Fixed the test host crash when --fail-fast stops a run with UI tests: the package depends on TUnit 1.76.45.