Migrate from MSTest Assert to Axiom¶
The MSTest path is focused on direct assertion shapes that map cleanly to Axiom. It is useful for scalar assertions, string checks, simple collection containment, collection uniqueness, ordered/range checks, type/reference checks, and awaited async exception assertions.
It is not intended to rewrite MSTest's broader collection comparison or message-heavy overloads automatically.
When This Migration Path Is A Good Fit¶
This path is a good fit when your MSTest suite uses straightforward Assert.*, StringAssert.*, or simple CollectionAssert.* checks.
It works best when tests are already written as direct facts: expected equals actual, value is null, string contains text, collection contains an item, value is in range, or async work throws a specific exception.
It is a weaker fit when the suite relies on CollectionAssert.AreEqual(...), CollectionAssert.AreEquivalent(...), comparer-heavy assertions, precision, message overloads, or broader assertion families.
What Axiom Can Rewrite Safely Today¶
The analyzer supports conservative rewrites for:
Assert.AreEqual(...)andAssert.AreNotEqual(...)Assert.IsNull(...),Assert.IsNotNull(...),Assert.IsTrue(...), andAssert.IsFalse(...)Assert.AreSame(...)andAssert.AreNotSame(...)Assert.IsInstanceOfType(...)andAssert.IsNotInstanceOfType(...)- newer
Assert.Contains(...),Assert.DoesNotContain(...),Assert.StartsWith(...), andAssert.EndsWith(...)string checks when the expected value is clear - newer
Assert.Contains(...)andAssert.DoesNotContain(...)collection checks StringAssert.Contains(...),StringAssert.StartsWith(...), andStringAssert.EndsWith(...)when the expected value is clearCollectionAssert.Contains(...)andCollectionAssert.DoesNotContain(...)CollectionAssert.AllItemsAreUnique(...)Assert.IsGreaterThan(...),Assert.IsGreaterThanOrEqualTo(...),Assert.IsLessThan(...),Assert.IsLessThanOrEqualTo(...), andAssert.IsInRange(...)- awaited
Assert.ThrowsExceptionAsync<TException>(...),Assert.ThrowsExactlyAsync<TException>(...), andAssert.ThrowsAsync<TException>(...)
Use the Analyzer reference when you need exact rule IDs.
A Recommended First Migration Pass¶
Start with the direct Assert.* calls first, then move outward.
A good first pass is:
- Rewrite equality, null, boolean, reference, and type checks.
- Rewrite simple
StringAssert.*checks. - Rewrite simple collection containment and uniqueness checks.
- Review ordered/range rewrites separately because MSTest uses bound-first argument order.
- Review awaited async exception rewrites separately.
- Leave structural collection comparisons and message-bearing overloads manual.
Before/After Examples¶
Scalar, type, and reference assertions:
Before:
Assert.AreEqual(expected, actual);
Assert.IsNull(value);
Assert.IsInstanceOfType(value, typeof(IDisposable));
After:
actual.Should().Be(expected);
value.Should().BeNull();
value.Should().BeAssignableTo<IDisposable>();
Reference identity follows the same direct pattern: Assert.AreSame(expected, actual) becomes actual.Should().BeSameAs(expected), and Assert.AreNotSame(expected, actual) becomes actual.Should().NotBeSameAs(expected).
String and collection containment assertions:
Before:
Assert.StartsWith("ord-", actual);
StringAssert.Contains(actual, "sub");
CollectionAssert.Contains(values, "expected");
CollectionAssert.AllItemsAreUnique(values);
After:
actual.Should().StartWith("ord-");
actual.Should().Contain("sub");
values.Should().Contain("expected");
values.Should().HaveUniqueItems();
Ordered and range assertions:
Before:
Assert.IsGreaterThan(minimum, count);
Assert.IsInRange(minimum, maximum, count);
After:
count.Should().BeGreaterThan(minimum);
count.Should().BeInRange(minimum, maximum);
Async exception assertions:
Before:
await Assert.ThrowsExceptionAsync<InvalidOperationException>(
async () => await Task.FromException(new InvalidOperationException()));
await Assert.ThrowsAsync<Exception>(
async () => await Task.FromException(new ArgumentException()));
After:
await new Func<Task>(() => Task.FromException(new InvalidOperationException()))
.Should()
.ThrowExactlyAsync<InvalidOperationException>();
await new Func<Task>(() => Task.FromException(new ArgumentException()))
.Should()
.ThrowAsync<Exception>();
What To Keep Manual For Now¶
Keep these manual during an MSTest migration:
CollectionAssert.AreEqual(...),CollectionAssert.AreEquivalent(...), and structural collection comparison- precision, comparer, and message-bearing overloads
- broader MSTest assertion families that are not direct scalar/string/containment/uniqueness checks
- async exception assertions that are not awaited
- xUnit-style async
paramNameandThrowsAnyAsync<TException>shapes, which MSTest does not expose - ordered assertions where the values are not clearly comparable
- structural object comparisons that should become
BeEquivalentTo(...)
Manual review is especially important for collection comparison. Containment is not the same thing as sequence equality or equivalency.
Practical Staged Rollout¶
A realistic MSTest rollout is:
- Pick one test project or namespace.
- Apply analyzer fixes for scalar
Assert.*calls. - Review string checks plus simple collection containment and uniqueness rewrites.
- Review ordered/range rewrites with attention to bound-first argument order.
- Review awaited async exception rewrites separately.
- Keep structural collection and object-graph assertions manual.
This avoids a noisy rewrite where the easy cases hide the tests that need actual design decisions.
When Not To Migrate Yet¶
Do not migrate yet if the suite mainly depends on MSTest collection equivalency, custom comparer behaviour, precision rules, or assertion messages that carry important debugging context.
In that case, first separate direct scalar checks from structural tests. The scalar checks can move later; structural tests should be designed deliberately.