Install
$ agentstack add skill-nice3point-revit-skills-revit-testing ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Revit Testing
Every Revit API call must run on the single thread that initialized Revit. Nice3point.TUnit.Revit marshals each test and hook onto that thread through the RevitThreadExecutor; the test body reads like ordinary API code. It builds on TUnit and Microsoft.Testing.Platform; running the tests needs a matching licensed Revit installation.
It applies to a project scaffolded from the revit-tunit template — with the project structure and the assembly-level RevitThreadExecutor already configured — and covers writing tests inside it, not setting the project up.
When to use
- Writing or reviewing a test for your own logic or helpers that call the Revit API.
- Asserting that an operation produced the expected model, file, or value.
When not to use
- Providing the document, service, or parameterized cases a test consumes — use
revit-test-fixtures. - The code under test never touches the Revit API — write a plain TUnit test with no
RevitApiTestbase and no executor.
Workflow
Step 1: Write a test on the Revit thread
Inherit RevitApiTest; it exposes the shared Application. TUnit constructs a new instance of the test class for every test; instance fields and properties are always isolated between tests — use them freely for per-test state. Structure the body as distinct Arrange, Act, and Assert blocks, and assert the observable result, not framework plumbing.
public sealed class BoundingBoxExtensionsTests : RevitApiTest
{
[Test]
public async Task Union_NonOverlappingBoxes_EnclosesBothExtents()
{
// Arrange
var first = new BoundingBoxXYZ { Min = new XYZ(0, 0, 0), Max = new XYZ(1, 1, 1) };
var second = new BoundingBoxXYZ { Min = new XYZ(2, 2, 2), Max = new XYZ(3, 3, 3) };
// Act
var union = first.Union(second);
// Assert
using (Assert.Multiple())
{
await Assert.That(union.Min.IsAlmostEqualTo(XYZ.Zero)).IsTrue();
await Assert.That(union.Max.IsAlmostEqualTo(new XYZ(3, 3, 3))).IsTrue();
}
}
}
Assert.Multiple() groups related checks so one failure does not hide the rest. TUnit assertions are awaited: IsEqualTo(...).Within(tol) for doubles, IsTrue(), IsNotEmpty(), .All().Satisfy(...) for collections, and .Throws() for failures.
Step 2: Keep every Revit-touching member on the Revit thread
The assembly-level [assembly: TestExecutor] (in TestsConfiguration.cs) runs every test on Revit's thread; individual tests need no attribute.
- A
[Before]/[After]hook that calls the Revit API carries[HookExecutor]. - A test that must run off the Revit thread overrides with its own
[TestExecutor]. - Load Revit API types lazily — a field initializer runs at construction, before Revit is injected:
// BAD — runs before Revit exists
private readonly ElementId _levelId = new ElementId(BuiltInCategory.OST_Levels);
// GOOD — resolved on first use, on the Revit thread
private ElementId LevelId => field ??= new ElementId(BuiltInCategory.OST_Levels);
Discovery happens before Revit is injected and off its thread: TUnit constructs the test class, evaluates every data source, and resolves every dependency-injection service at discovery. No constructor, field initializer, data-source member, or injected service may touch the Revit API at construction — defer that work to the test body or a [Before] hook that runs on the Revit thread.
Step 3: Parameterize and supply fixtures
Feed a small fixed set of primitive cases inline with [Arguments]; the test body builds the Revit objects. [Arguments], method data sources, and custom data sources are basic TUnit features; this skill adds the Revit-thread constraints. For data-source choices beyond the Revit fixtures below, read TUnit's Method Data Sources source.
[Test]
[Arguments(3, 4, 5)]
[Arguments(0, 3, 4)]
public async Task NewXyz_Distance_MatchesLength(double x, double y, double z)
{
// Arrange
var expected = Math.Sqrt(x * x + y * y + z * z);
// Act
var point = Application.Create.NewXYZ(x, y, z);
// Assert
await Assert.That(point.DistanceTo(XYZ.Zero)).IsEqualTo(expected).Within(1e-6);
}
A data source runs during TUnit discovery, off the Revit thread; it yields plain inputs (numbers, strings, file paths) and never a Revit object. For a seeded model, an opened sample file, an injected service, or the same test across many file kinds, use revit-test-fixtures — it routes each situation to the right fixture and data source.
Step 4: Run against a matching Revit install
TUnit runs on Microsoft.Testing.Platform, and the configuration carries the target Revit version.
dotnet test -c Release.RNN
RNN is the target Revit-year configuration, for example Release.R26. Use dotnet run -c Release.RNN for simpler command-line flag passing. A licensed Revit matching the selected configuration must be installed, because the tests run against a real Revit process.
Validation
- [ ] Tests inherit
RevitApiTestand assert observable model behavior, not framework plumbing. - [ ] Every Revit-touching hook carries
[HookExecutor]. - [ ] Data sources and inline arguments carry only primitives; Revit objects are built in the test body.
- [ ] Tests do not reference
RevitAPIUItypes that require a UI session. - [ ] The selected
Release.RNNconfiguration matches the installed Revit runtime.
Common Pitfalls
| Pitfall | Correct approach | |--------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------| | Revit API called with no executor (thread error) | Keep the assembly-level TestExecutor; add [TestExecutor] only to override it. | | A Revit-touching [Before]/[After] hook without a hook executor | Add [HookExecutor]. | | A data source that returns a Revit object | Data sources run off the Revit thread at discovery; return primitives or paths and build Revit objects in the body. | | A field initializer that loads a Revit API type | Field initializers run before Revit is injected; move the value into a lazy field ??= … property. | | A test references a RevitAPIUI type that requires a UI session | Reference only RevitAPI in tests; RevitAPIUI needs a UI session the test host lacks. | | Asserting framework plumbing | Assert the resulting model, file, or value. | | RevitApiTest not found | The Nice3point.TUnit.Revit package is not referenced. |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Nice3point
- Source: Nice3point/revit-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.