How to add custom message to Jest expect?

JavascriptUnit TestingJestjs

Javascript Problem Overview


Image following test case:

it('valid emails checks', () => {
  ['[email protected]', '[email protected]'/*, ...*/].map(mail => {
    expect(isValid(mail)).toBe(true);
  });
});

I would like to add auto-generated message for each email like Email '[email protected]' should be valid so that it's easy to find failing test cases.

Something like:

// .map(email =>
expect(isValid(email), `Email ${email} should be valid`).toBe(true);

Is it possible in Jest ?

In Chai it was possible to do with second parameter like expect(value, 'custom fail message').to.be... and in Jasmine seems like it's done with .because clause. But cannot find solution in Jest.

Javascript Solutions


Solution 1 - Javascript

You try this lib that extends jest: https://github.com/mattphillips/jest-expect-message

test('returns 2 when adding 1 and 1', () => {
  expect(1 + 1, 'Woah this should be 2!').toBe(3);
});

Solution 2 - Javascript

I don't think it's possible to provide a message like that. But you could define your own matcher.

For example you could create a toBeValid(validator) matcher:

expect.extend({
  toBeValid(received, validator) {
    if (validator(received)) {
      return {
        message: () => `Email ${received} should NOT be valid`,
        pass: true
      };
    } else {
      return {
        message: () => `Email ${received} should be valid`,
        pass: false
      };
    }
  }
});

And then you use it like this:

expect(mail).toBeValid(isValid);

Note: toBeValid returns a message for both cases (success and failure), because it allows you to use .not. The test will fail with the corresponding message depending on whether you want it to pass the validation.

expect(mail).toBeValid(isValid);
// pass === true: Test passes
// pass === false: Failure: Email ... should be valid

expect(mail).not.toBeValid(isValid);
// pass === true: Failure: Email ... should NOT be valid
// pass === false: Test passes

Solution 3 - Javascript

Although it's not a general solution, for the common case of wanting a custom exception message to distinguish items in a loop, you can instead use Jest's test.each.

For example, your sample code:

it('valid emails checks', () => {
  ['[email protected]', '[email protected]'/*, ...*/].map(mail => {
    expect(isValid(mail)).toBe(true);
  });
});

Could instead become

test.each(
    ['[email protected]', '[email protected]'/*, ...*/],
    'checks that email %s is valid',
    mail => {
        expect(isValid(mail)).toBe(true);
    }
);

Solution 4 - Javascript

You can use try-catch:

try {
    expect(methodThatReturnsBoolean(inputValue)).toBeTruthy();
}
catch (e) {
    throw new Error(`Something went wrong with value ${JSON.stringify(inputValue)}`, e);
}

Solution 5 - Javascript

2021 answer

I did this in some code I was writing for Mintbean by putting my it blocks inside forEach.

By doing this, I was able to achieve a very good approximation of what you're describing.

Pros:

  • Excellent "native" error reports
  • Counts the assertion as its own test
  • No plugins needed.

Here's what your code would look like with my method:


// you can't nest "it" blocks within each other,
// so this needs to be inside a describe block. 
describe('valid emails checks', () => {
  ['[email protected]', '[email protected]'/*, ...*/].forEach(mail => {
    // here is where the magic happens
    it(`accepts ${mail} as a valid email`, () => {
      expect(isValid(mail)).toBe(true);
    })
  });
});

Errors then show up like this.

Notice how nice these are!

 FAIL  path/to/your.test.js
  ● valid emails checks › accepts abc@y.com as a valid email

    expect(received).toBe(expected)

    Expected: "[email protected]"
    Received: "[email protected]"

      19 |    // here is where the magic happens
      20 |    it(`accepts ${mail} as a valid email`, () => {
    > 21 |      expect(isValid(mail)).toBe(true);
                                       ^
      22 |    })

Solution 6 - Javascript

Just had to deal with this myself I think I'll make a PR to it possibly: But this could work with whatever you'd like. Basically, you make a custom method that allows the curried function to have a custom message as a third parameter.

It's important to remember that expect will set your first parameter (the one that goes into expect(akaThisThing) as the first parameter of your custom function.

For a generic Jest Message extender which can fit whatever Jest matching you'd already be able to use and then add a little bit of flourish:

expect.extend({
  toEqualMessage(received, expected, custom) {
    let pass = true;
    let message = '';
    try {
      // use the method from Jest that you want to extend
      // in a try block
      expect(received).toEqual(expected);
    } catch (e) {
      pass = false;
      message = `${e}\nCustom Message: ${custom}`;
    }
    return {
      pass,
      message: () => message,
      expected,
      received
    };
  }
});

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace jest {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    interface Matchers<R> {
      toEqualMessage(a: unknown, b: string): R;
    }
  }
}

Will show up like:

    Error: expect(received).toEqual(expected) // deep equality

    Expected: 26
    Received: 13
    Custom Message: Sad Message Indicating failure :(

For specific look inside the expect(actualObject).toBe() in case that helps your use case:

import diff from 'jest-diff'

expect.extend({
toBeMessage (received, expected, msg) {
  const pass = expected === received
  const message = pass
? () => `${this.utils.matcherHint('.not.toBe')}\n\n` +
        `Expected value to not be (using ===):\n` +
        `  ${this.utils.printExpected(expected)}\n` +
        `Received:\n` +
        `  ${this.utils.printReceived(received)}`
      : () => {
        const diffString = diff(expected, received, {
          expand: this.expand
        })
        return `${this.utils.matcherHint('.toBe')}\n\n` +
        `Expected value to be (using ===):\n` +
        `  ${this.utils.printExpected(expected)}\n` +
        `Received:\n` +
        `  ${this.utils.printReceived(received)}` +
        `${(diffString ? `\n\nDifference:\n\n${diffString}` : '')}\n` +
        `${(msg ? `Custom:\n  ${msg}` : '')}`
      }

    return { actual: received, message, pass }
  }
})

// usage:
expect(myThing).toBeMessage(expectedArray, ' was not actually the expected array :(')

Solution 7 - Javascript

Another way to add a custom error message is by using the fail() method:

it('valid emails checks', (done) => {
  ['[email protected]', '[email protected]'/*, ...*/].map(mail => {
    if (!isValid(mail)) {
      done.fail(`Email '${mail}' should be valid`)
    } else {
      done()
    }
  })
})

Solution 8 - Javascript

I end up just testing the condition with logic and then using the fail() with a string template.

i.e.

it('key should not be found in object', () => {
    for (const key in object) {
      if (Object.prototype.hasOwnProperty.call(object, key)) {
        const element = object[key];
        if (element["someKeyName"] === false) {
          if (someCheckerSet.includes(key) === false) {
            fail(`${key} was not found in someCheckerSet.`)
          }
        }

Solution 9 - Javascript

To expand on @Zargold's answer:

> For more options like the comment below, see MatcherHintOptions doc

// custom matcher - omit expected
expect.extend({
  toBeAccessible(received) {
    if (pass) return { pass };
    return {
      pass,
      message: () =>
        `${this.utils.matcherHint('toBeAccessible', 'received', '', {
          comment: 'visible to screen readers',
        })}\n
Expected: ${this.utils.printExpected(true)}
Received: ${this.utils.printReceived(false)}`,
    };
  }

enter image description here

// custom matcher - include expected
expect.extend({
  toBeAccessible(received) {
    if (pass) return { pass };
    return {
      pass,
      message: () =>
        `${this.utils.matcherHint('toBeAccessible', 'received', 'expected', { // <--
          comment: 'visible to screen readers',
        })}\n
Expected: ${this.utils.printExpected(true)}
Received: ${this.utils.printReceived(false)}`,
    };
  }

enter image description here

Solution 10 - Javascript

you can use this: (you can define it inside the test)

      expect.extend({
ToBeMatch(expect, toBe, Msg) {  //Msg is the message you pass as parameter
    const pass = expect === toBe;
    if(pass){//pass = true its ok
        return {
            pass: pass,
            message: () => 'No ERRORS ',
          };
    }else{//not pass
        return {
            pass: pass,
            message: () => 'Error in Field   '+Msg + '  expect  ' +  '  ('+expect+') ' + 'recived '+'('+toBe+')',
          };
    }
},  });

and use it like this

     let z = 'TheMassageYouWantWhenErrror';
    expect(first.name).ToBeMatch(second.name,z);

Solution 11 - Javascript

You can rewrite the expect assertion to use toThrow() or not.toThrow(). Then throw an Error with your custom text. jest will include the custom text in the output.

// Closure which returns function which may throw
function isValid (email) {
  return () => {
     // replace with a real test!
     if (email !== '[email protected]') {
       throw new Error(`Email ${email} not valid`)
     }
  }
}

expect(isValid(email)).not.toThrow()

Solution 12 - Javascript

I'm usually using something like

it('all numbers should be in the 0-60 or 180-360 range', async () => {
	const numbers = [0, 30, 180, 120];
	for (const number of numbers) {
		if ((number >= 0 && number <= 60) || (number >= 180 && number <= 360)) {
			console.log('All good');
		} else {
			expect(number).toBe('number between 0-60 or 180-360');
		}
	}
});

Generates: enter image description here

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJuroshView Question on Stackoverflow
Solution 1 - JavascriptAlexey VolkovView Answer on Stackoverflow
Solution 2 - JavascriptMichael JungoView Answer on Stackoverflow
Solution 3 - JavascriptJosh KelleyView Answer on Stackoverflow
Solution 4 - JavascriptMikkView Answer on Stackoverflow
Solution 5 - JavascriptMonarch WadiaView Answer on Stackoverflow
Solution 6 - JavascriptZargoldView Answer on Stackoverflow
Solution 7 - JavascriptIlyichView Answer on Stackoverflow
Solution 8 - JavascriptK.H. BView Answer on Stackoverflow
Solution 9 - JavascriptpiousonView Answer on Stackoverflow
Solution 10 - JavascriptDaniel CohenView Answer on Stackoverflow
Solution 11 - JavascriptMark StosbergView Answer on Stackoverflow
Solution 12 - JavascriptJan DočkalView Answer on Stackoverflow