AnyUnit.Style.MsTest
1.2.2
dotnet add package AnyUnit.Style.MsTest --version 1.2.2
NuGet\Install-Package AnyUnit.Style.MsTest -Version 1.2.2
<PackageReference Include="AnyUnit.Style.MsTest" Version="1.2.2" />
<PackageVersion Include="AnyUnit.Style.MsTest" Version="1.2.2" />
<PackageReference Include="AnyUnit.Style.MsTest" />
paket add AnyUnit.Style.MsTest --version 1.2.2
#r "nuget: AnyUnit.Style.MsTest, 1.2.2"
#:package AnyUnit.Style.MsTest@1.2.2
#addin nuget:?package=AnyUnit.Style.MsTest&version=1.2.2
#tool nuget:?package=AnyUnit.Style.MsTest&version=1.2.2
AnyUnit.Style.MsTest
Roughly MSTest-compatible attributes and assertions for AnyUnit - close enough to real MSTest's own syntax that a test's logic often doesn't need to change, run anywhere AnyUnit's core reaches (including browser-wasm) - not a full reimplementation of MSTest's API (see "Not covered" below).
Unlike AnyUnit.Style.Nunit and AnyUnit.Style.Xunit, nothing in this
package is ported from the framework it imitates; every file is written
against MSTest's documented public shape, so the whole directory carries
AnyUnit's own Apache-2.0 header and there is no License.txt here.
Moving an existing MSTest suite onto this
Usually a PackageReference swap plus two edits per file:
using Microsoft.VisualStudio.TestTools.UnitTesting;becomesusing AnyUnit.Style.MsTest;- Each
[TestClass]must derive fromAssertionHelper:
using AnyUnit.Style.MsTest;
[TestClass]
public class CalculatorTests : AssertionHelper // <- the one required edit
{
[TestInitialize]
public void Setup() { ... }
[TestMethod]
public void Adds()
{
Assert.AreEqual(4, 2 + 2);
StringAssert.Contains("hello world", "lo wo");
CollectionAssert.AreEqual(new[] { 1, 2 }, Calculator.Pair());
}
}
That base class is the porting tax, and it is the same one
AnyUnit.Style.Nunit's AssertionHelper and AnyUnit.Style.Xunit's
TestClass already charge: a real [TestClass] derives from nothing,
because MSTest's Assert, StringAssert and CollectionAssert are
static classes. AnyUnit's Assert is deliberately an instance scoped to
one test's run, so that a test which asserted nothing can be told apart
from one that asserted and passed (NoError vs Success - see
AnyUnit's own README for why that distinction is worth
having). Deriving AssertionHelper is what makes the bare identifiers
Assert, StringAssert, CollectionAssert and TestContext inside a
test method resolve, unchanged, to that per-test instance.
Once the class derives it, ordinary call sites carry over verbatim -
Assert.AreEqual(...), CollectionAssert.AreEquivalent(...),
TestContext.WriteLine(...) all mean the same thing and read the same
way.
Covers
[TestClass],[TestMethod],[DataTestMethod][TestInitialize]/[TestCleanup],[ClassInitialize]/[ClassCleanup][AssemblyInitialize]/[AssemblyCleanup]- see the caveat below[DataRow](including[DataRow(null)]),[DynamicData](property or method source, own type or a foreign one)[Ignore](method and class),[Timeout],[Description],[TestCategory],[Owner],[Priority],[TestProperty][ExpectedException], includingAllowDerivedTypesand MSTest's exact-type-match defaultAssert.AreEqual/AreNotEqual(including thedouble+ delta andstring+ignoreCaseoverloads),AreSame/AreNotSame,IsTrue/IsFalse,IsNull/IsNotNull,IsInstanceOfType/IsNotInstanceOfType,ThrowsException<T>,Fail,InconclusiveStringAssert.Contains/DoesNotContain/StartsWith/EndsWith/Matches/DoesNotMatchCollectionAssert.AreEqual/AreNotEqual/AreEquivalent/AreNotEquivalent/Contains/DoesNotContain/AllItemsAreNotNull/AllItemsAreUnique/IsSubsetOf- A minimal
TestContext:TestName,FullyQualifiedTestClassName,CurrentTestOutcome,WriteLine
Assert.Inconclusive maps onto AnyUnit.IgnoreException, so an
inconclusive test is reported as skipped by every format
AnyUnit.Report writes - an exact mapping, not an
approximation.
Cross-style data rows
[DataRow] implements AnyUnit.Run.Attributes.IRowInlineParameter and
[DynamicData] implements IGeneratingParameter, which are core
interfaces every style package already references. So a
[DataTestMethod] accepts xUnit [InlineData] and NUnit [TestCase]
rows, and [DataRow] feeds an xUnit [Theory] or an NUnit [Test] -
with no reference between the style packages in either direction.
WhoTestsTheTesters/Tests/Style/ComboTests keeps that honest.
Not covered
[AssemblyInitialize]needs one extra attribute. AnyUnit discovers its once-per-scope hook (SetUpFixtureAttributeBase) as a class-level attribute, whereas MSTest infers the class from the marked methods, so the holding class must also be marked[AssemblySetUp]:[TestClass] [AssemblySetUp] // <- not an MSTest attribute public class Global { [AssemblyInitialize] public static void Init(TestContext context) { ... } }Also note AnyUnit scopes that to the class's own namespace and its sub-namespaces, not to the literal assembly. Put the class in the suite's root namespace - where a ported one almost always already sits - and the two coincide.
TestContextstops at four members.TestName,FullyQualifiedTestClassName,CurrentTestOutcomeandWriteLineare there because ported suites read them and because[ClassInitialize]'s signature does not compile without the type existing at all. The deployment API (TestDeploymentDir,TestRunDirectory,DeploymentDirectory,AddResultFile) is absent because AnyUnit has no deployment step - useAppContext.BaseDirectoryfor a path. The old database-backed data-driven API (TestContext.DataRow,DataConnection,Properties) is absent because[DataRow]/[DynamicData]cover what people still write.BeginTimer/EndTimerare absent.CurrentTestOutcomeis coarse. It reportsInProgress, thenPassed,Failed,InconclusiveorError.Timeout,Aborted,NotRunnableandUnknownexist in the enum for source compatibility but are never produced - a timeout is enforced by the core outside the test's own invocation (seeAnyUnit.Run.Test.Run), so there is no moment at which a[TestCleanup]could observe one.[TestProperty],[Owner]and[Priority]are flattened into categories ("key=value","Owner:name","Priority:1") because the results schema carries only a flat category list today. Real key/value properties are a separate in-flight change; when that lands these move and only true[TestCategory]names stay in the category list.[DataRow(..., DisplayName = "...")]compiles but is ignored. AnyUnit names a row from its arguments and has no slot for an explicit display name.No parameterized test classes - MSTest has no such concept, so
[TestClass]takes no constructor arguments.Other MSTest attributes -
[DeploymentItem],[TestInitialize]ordering attributes,[DoNotParallelize]/[Parallelize](AnyUnit's runner is strictly sequential, so parallelism attributes would be meaningless),[STATestMethod],[CssIteration]/[CssProjectStructure], and[WorkItem]are not implemented.No static
Assert. Same deliberate design as every other AnyUnit style: a shared/global assert cannot tell a test that made real assertions apart from one that made none. A plain helper class that is not itself a fixture and wants to assert throwsAnyUnit.AssertionException/AnyUnit.IgnoreExceptiondirectly.async Tasktest methods. AnyUnit's core does not await a returnedTaskyet, so anasynctest's failures can go unnoticed. That is a core limitation, not a MSTest-style one, and is being fixed separately.
Validated against a real suite
Ported ParksComputing.Xfer.Lang.Tests
- 595 test methods, 50 test classes, 60 files - which passes 595/595 under real MSTest 3.9.3.
After the port it runs on AnyUnit with the same 595 tests and no failures.
The whole port, end to end:
| Edit | Count |
|---|---|
<Using Include="...UnitTesting" /> → AnyUnit.Style.MsTest in the csproj |
1 |
Swap the two MSTest PackageReferences for a reference to this package |
1 |
using Microsoft.VisualStudio.TestTools.UnitTesting; → using AnyUnit.Style.MsTest; |
50 files |
Add : AssertionHelper to each [TestClass] |
50 classes |
Cast a dynamic argument (see below) |
3 call sites |
Everything else - [TestMethod], [TestClass], [TestInitialize],
[TestCleanup], 23 [ExpectedException], and ~1,570 assertion calls
across Assert, StringAssert and CollectionAssert - compiled and
ran unchanged.
Two gaps it found, both now fixed
Assert.IsInstanceOfType<T>(value) (MSTest 3.x's generic overload, as
opposed to the (value, Type) one) and Assert.ThrowsExceptionAsync<T>
were both missing. Added.
One limit that cannot be fixed
Assert here is a set of extension methods on IAssert, and C# cannot
dispatch an extension method on a dynamic argument:
dynamic? result = Deserialize<dynamic>(text);
Assert.IsNotNull(result); // CS1973 here, fine in real MSTest
Assert.IsNotNull((object?)result); // the fix: cast
Real MSTest's Assert is a static class, so it has no such problem. This
is inherent to how a style adds vocabulary to IAssert and is the one
edit a port may need beyond the base class. It affected 3 call sites out
of ~1,570.
What it also surfaced about the suite
Five of the 595 tests report NoError rather than Success - they
assert nothing at all. One is a debug leftover that only writes to the
console inside a try/catch; another is a "does not throw" test with no
assertion. Both are green under MSTest, which cannot tell them apart
from a real pass. Distinguishing exactly that is why AnyUnit's Assert
is an instance scoped to one test.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- AnyUnit (>= 1.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.