Skip to main content

Message conventions

A failure message of aweXpect reads like one English sentence. The built-in expectations follow the conventions on this page, so that an extension that follows them as well reads like part of the library.

The samples on this page use the following namespaces:

using System.Text;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Formatting;
using static aweXpect.Formatting.Format;

Shape of a failure message​

aweXpect composes the failure message from the subject, the expectation texts and the result texts of the constraints, and the reason from Because(…):

Failure message
Expected that <subject>
<expectation>[, because <reason>],
but <result>

Your constraint only writes its expectation text (AppendExpectation) and its result text (AppendResult); the rest is added around them. For the IsAbsolutePath expectation from constraints and results, await Expect.That(path).IsAbsolutePath() fails with:

Failure message
Expected that path
is an absolute path,
but it was "album.txt"

The same texts are reused when the expectation is combined or nested, e.g. inside Whose, where it is replaced by the name of the member:

Failure message
Expected that playlist
whose Path is an absolute path,
but Path was "album.txt"

Expectation text​

  • Start with the verb in the present tense, in lower case, and end without punctuation: is an absolute path, is equal to "Abbey Road", starts with "Abbey", has flag A.
  • Write the negated text with not: is not an absolute path, does not start with "Abbey".
  • Append the options after the expectation, e.g. ignoring case, using MyComparer or in any order.
  • Describe the expected value, not the check: is an absolute path instead of Path.IsPathRooted returns true.

Result text​

  • Start with the name of the subject, the it parameter of the constraint (exposed as It by the helper classes), followed by a verb in the past tense: it was "album.txt", it had 3 items, Path was "album.txt". The name is it or the name of a member, so never write the subject yourself.
  • Describe what was found instead, without repeating the expectation. A short elliptical result is normal: it was, it did, it was not.
  • Add a detail after a comma: , which differs at index 12, , which differs by 2.
  • Put longer information, such as the items of a collection, in a context below the message instead (see contexts).

Some results are written for you:

SituationResult textWritten by
a null subjectit was <null>ConstraintResult.WithNotNullValue<T>
the evaluation was canceledit could not be verified, because the evaluation was already canceledAppendCanceledResult, the default of AppendUndecidedResult
code of the caller threwthe predicate did throw an InvalidOperationExceptionUserCode.Invoke

Formatting values​

Format every value with Formatter.Format from aweXpect.Formatting.Format, so that it looks the same as in the built-in messages and respects the formatting settings:

ValueFormatted as
null<null>
string"Let It Be", in quotes and truncated after MaximumStringLength characters
char'a'
numbers42 or -3.5, independent of the current culture
TimeSpan0:02 or 0:00.015
DateTime2024-12-24T13:15:00.0000000
Typeits C# name without namespace, e.g. int or List<string>
Exceptionits type and message, e.g. InvalidOperationException: Yesterday
collection["Help!", "Revolver"], with (… and 2 more) after MaximumNumberOfCollectionItems items
other objectstheir ToString() if it is overridden, otherwise their public members, e.g. Album { Title = "Abbey Road" }

The FormattingOptions change the layout: FormattingOptions.MultipleLines puts every item of a collection on its own line, e.g. for a context, FormattingOptions.WithType prefixes the type (int[] [1, 2]), and FormattingOptions.Indented(indentation) indents the following lines. Register an IValueFormatter to format your own types, see initialization.

Vocabulary​

Name the expectation methods like the built-in ones, so that the whole chain reads like a sentence:

PatternUse it forExamples
Is…, IsNot…a state or a comparison of the subjectIsEmpty, IsNotEqualTo, IsAbsolutePath
Has…a property of the subject, optionally with a comparisonHasLength(3), HasCount().GreaterThan(2)
DoesNot…the negation of a verbDoesNotContain, DoesNotStartWith
With…a property of the result of the previous expectationThrows<T>().WithMessage(…)
Which, Whosecontinuing with a new subject, or with a member of the subjectHasSingle().Which, Whose(x => x.Title, …)
Ignoring…, Using, Within, In…Orderoptions of the previous expectationIgnoringCase(), Using(comparer), InAnyOrder()

With… continues the sentence of the previous expectation, "throws a CustomException with message …", while Has… starts a new sentence about the subject. Offer both when your subject can appear in both positions, like the exception expectations do.

Grammar​

The grammars a constraint receives tell it how its texts are used in the sentence. They are an ExpectationGrammars flags enum:

FlagSet when
Negatedthe expectation is negated; the helper classes then call AppendNegatedExpectation and AppendNegatedResult
Pluralthe subject of the sentence is plural, e.g. whose Files are absolute paths for all items
Nestedthe expectation continues the sentence of another one, e.g. for a member or an item
Activethe expectation continues a With… clause, which drops the verb, e.g. with message equal to "bar"
Introducedthe subject was already introduced by a connector like the that of has item that

Active has the opposite meaning for the expectation text of a string match type (IStringMatchType.GetExpectation): there it asks for the text with the verb (is equal to "bar"), and without it the text starts with the comparison (equal to "bar").

ExpectationGrammarsExtensions checks them with IsNegated(), IsNested(), IsPlural() or HasAnyFlag(…), and toggles the negation with Negate(). Use the plural form of the verb in the expectation text when the grammars are plural, but keep the singular form in the result text as long as the subject is the pronoun it:

private sealed class IsAbsolutePathConstraint(string it, ExpectationGrammars grammars)
: ConstraintResult.WithNotNullValue<string>(it, grammars),
IValueConstraint<string>
{
public ConstraintResult IsMetBy(string actual)
{
Actual = actual;
Outcome = Path.IsPathRooted(actual) ? Outcome.Success : Outcome.Failure;
return this;
}

protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
=> stringBuilder.Append(Grammars.IsPlural() ? "are absolute paths" : "is an absolute path");

protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append(It).Append(Grammars.IsPlural() && It != "it" ? " were " : " was ");
Formatter.Format(stringBuilder, Actual);
}

protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
=> stringBuilder.Append(Grammars.IsPlural() ? "are not absolute paths" : "is not an absolute path");

protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
=> AppendNormalResult(stringBuilder, indentation);
}

Contexts​

Information that does not fit into one sentence, such as the items of a collection or the expected and the actual value of a long comparison, belongs in a context. A context is shown below the message with its title, e.g. Collection:, Expected:, Actual: or Not matching items:. Use the overload of AddConstraint that also passes the ExpectationBuilder, and add the context while the constraint is evaluated:

expectationBuilder.AddContext(new ResultContext.Fixed("Playlist", Formatter.Format(files, FormattingOptions.MultipleLines)));

Exceptions​

An exception that rejects an invalid argument is a complete sentence that starts with The and ends with a period, e.g. The maximum must be greater than or equal to the minimum. or The tolerance must be a whole number of days..