Customize Assertion Error Messages

How can I add additional context to an assertion error's message?

Instead of trying to provide facilities to fully customize the error message, Moat Maker strives to simply deliver good and helpful error messages. If you feel a particular error message could be improved in some way, please submit an issue and let us know.

That being said, it's still important to provide ways to add additional context to an error. For this reason, two different parameters have been made available to give you this power: at and errorPrefix. To show how these behave, let's start with a very simple validator:

const hasUsernameValidator = validator`{ name: string }`;

If we call .assertMatches() without customizing the error at all, we'll be given the following error message:

// TypeError: Expected <receivedValue>.name to be of
// type "string" but got type "boolean".
hasUsernameValidator.assertMatches({ name: false });

The "errorPrefix" Assert Parameter

Using errorPrefix, we can add arbitrary information to the start of the error string. Note that the errorPrefix string provided absolutely must end with a colon.

// TypeError: Error in myConfig.json: Expected
// <receivedValue>.name to be of type "string" but
// got type "boolean".
hasUsernameValidator.assertMatches({ name: false }, {
  errorPrefix: 'Error in myConfig.json:',
});

The "at" Assert Parameter

We can also provide a custom at field to change the default <receivedValue> text to be whatever we want. This serves two purposes. 1. You can use something more helpful than the extremely generic "receivedValue" text. 2. If you're validating a value that's part of a deeper object structure, you can provide information about the path already taken to get to where you are. This example illustrates both of these use cases.

const configData = {
  user: { name: false },
};

// TypeError: Expected <myConfig.json>.user.name to be
// of type "string" but got type "boolean".
hasUsernameValidator.assertMatches(configData.user, {
  at: '<myConfig.json>.user',
});

.assertArgs()

For the common scenario of validating user input for your public API, a special .assertArgs() function is provided that's pre-configured to provide appropriate "prefix" and "at" fields for you. For the most descriptive errors, it's best to use .assertArgs() with a validator that matches against a tuple with named entries, however, any validator can be used.

function getName(userInfo: { name: string }) {
  validator`[userInfo: { name: string }]`
    .assertArgs('getName()', arguments);

  return userInfo.name;
}

// TypeError: Received invalid "userInfo" argument for getName():
// Expected <1st argument>.name to be of type "string" but got type "boolean".
getName({ name: false });

.assertArgs() takes two parameters. The first is the name of the function we're validating the input for. Some examples might be "getName()", "MyClass.myStaticMethod()", "<date instance>.getDay()", or whatever descriptive name you can think of to help someone receiving this error know where the issue happened at. The second argument should be the complete list of arguments your input function takes. You can conveniently use the arguments object to accomplish this purpose. If you do not wish to use the arguments object, or if you can not (e.g. because you're using an arrow function which does not support it), you can instead choose to use an alternative pattern, like spreading and destructuring:

function getName(...allArgs: [userInfo: { name: string }]) {
  const [userInfo] = allArgs;
  validator`[userInfo: { name: string }]`
    .assertArgs('getName()', allArgs);

  return userInfo.name;
}

Note: In the above getName() examples, getName() must be called with exactly one argument. Conventionally, you're allowed to call a JavaScript function with extra arguments, and JavaScript will silently ignore them. Allowing and ignoring extra arguments can make some JavaScript patterns easier, for example, you can do arrayOfInfo.map(getName), which will auto-call getName() with each entry in the array, along with an index and a reference to the array itself. getName() will ignore the latter two arguments and only operate on the first. If you wish to allow your end-users to use such patterns, use the rest syntax inside your tuple definition, like this:

validator`[userInfo: { name: string }, ...etc: unknown]`

Be warned that enabling this pattern can make it harder to make non-breaking updates to your library. If, later, you want to allow your function to accept additional arguments, you might not be able to do so, because your users may already be passing in additional throw-away arguments. For this reason, it is discouraged to ever provide extra arguments to a function. If interested, here's a discussion related to this topic, which ends with a JavaScript committee member stating that developers should avoid this practice for this same reason.

What Else Can You Customize?

By default, a TypeError is thrown when an assertion fails. If you need to throw a different error, you can use the errorFactory parameter. See the API reference for more details.

Last updated