How to add custom message to Jest expect?

Viewed 50942

Image following test case:

it('valid emails checks', () => {
  ['abc@y.com', 'a@b.nz'/*, ...*/].map(mail => {
    expect(isValid(mail)).toBe(true);
  });
});

I would like to add auto-generated message for each email like Email 'f@f.com' 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.

13 Answers

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', () => {
  ['abc@y.com', 'a@b.nz'/*, ...*/].map(mail => {
    expect(isValid(mail)).toBe(true);
  });
});

Could instead become

test.each(['abc@y.com', 'a@b.nz'/*, ...*/])(
    'checks that email %s is valid',
    mail => {
        expect(isValid(mail)).toBe(true);
    }
);

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', () => {
  ['abc@y.com', 'a@b.nz'/*, ...*/].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: "abc@y.com"
    Received: "xyz@y.com"

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

You can use try-catch:

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

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

it('valid emails checks', (done) => {
  ['abc@y.com', 'a@b.nz'/*, ...*/].map(mail => {
    if (!isValid(mail)) {
      done.fail(`Email '${mail}' should be valid`)
    } else {
      done()
    }
  })
})

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.`)
          }
        }

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

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);

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 !== 'some@example.com') {
       throw new Error(`Email ${email} not valid`)
     }
  }
}

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

Instead of using the value, I pass in a tuple with a descriptive label. For example, when asserting form validation state, I iterate over the labels I want to be marked as invalid like so:

errorFields.forEach((label) => {
  const field = getByLabelText(label);

  expect(field.getAttribute('aria-invalid')).toStrictEqual('true');
});

Which gives the following error message:

expect(received).toStrictEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Array [
        "Day",
    -   "false",
    +   "true",
      ]

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

Related