77

Is there a way to print a custom error message when a Jasmine expect() fails?

As an example, for end to end testing I have an array of web pages and I use one test to go to each URL and assert an element exists on each page. I know I can put every expect() into a separate test, but I'd rather iterate through the array and log the page URL on failure.

apxcode
  • 7,696
  • 7
  • 30
  • 41
user3517049
  • 826
  • 1
  • 6
  • 9

10 Answers10

71

UPDATE

I see people still are finding this. Later information from the Jasmine team is that there is an undocumented feature on the expect - you can include a custom failure message and it just works:

expect( fields[i].element.exists() ).toEqual(true, field[i].name + ' is expected to exist');

Which is exactly what I was originally looking for.

Original answer follows:

I've been looking for exactly this today, and put a comment here: https://github.com/adobe/brackets/issues/2752

The syntax that has been discussed is an extension to Jasmine to permit a because to be added - so you'd be able to write:

expect( fields[i].element.exists() ).toEqual(true).because( field[i].name + 'is expected to exist');

That is still being discussed after a few years, and may not come to fruition. Another way that I've found to do this is to create a custom matcher. In general I think I'd discourage a custom matcher without being sure you're covering all the bases with it, but in this case we're really checking a true/false value, so the matcher isn't too scary.

We can create the custom matcher with a beforeEach:

beforeEach(function() {
  var matchers = {
    toEqualBecause: function( value, message ) {
      this.message = function() {
        return "Expected '" + this.actual + "' to equal '" + value + "' because " + message;  
      };

      return this.actual == value;  
    }
 };

  this.addMatchers(matchers);
});

We can then use this matcher to put a message with our failures as such:

expect( field[i].element.exists() ).toEqualBecause( true, field[i].name );

Which will give a failure output including the field name as such:

Expected 'false' to equal 'true' because account_name
Gopherkhan
  • 4,317
  • 4
  • 32
  • 54
PaulL
  • 6,650
  • 3
  • 35
  • 39
  • awesome find! expect(something).toBeFalsy('should not...'); works too – ajd Apr 24 '15 at 13:16
  • Hi! Surely you should place your latest discovery on top of your answer, so it's directly accessible (leave the rest as historic logs below). Thx. – Bob Feb 13 '18 at 19:53
  • 1
    As of jasmine 3.3.0 it seems to work with `toBe`, but not with `toEqual` – Raketenolli Dec 12 '18 at 09:01
  • Still not working with ```toEqual```, despite it is documented. With ```toBe``` you test exactly the same values which fails on objects (maybe because the prototype part) so still inapplicable :( – Anton Mitsev Mar 14 '19 at 15:48
  • I think it is ridiculous this is 'still being discussed'. This is an obvious feature of test frameworks and has been since their inception. Thanks for showing the workaround. – absmiths Oct 04 '19 at 14:15
  • See my comment https://stackoverflow.com/a/54343357/2194590 – HolgerJeromin Oct 27 '20 at 14:18
36

Yes, we can print a custom error message when an expect() failed in Jasmine.

 Code Snippet:

  it('print a custom error message when an expect failed', function() {

    var elemenToBeDisplayed=element(by.css("userName"));

    /*custom error message will be displayed if expected condition 
    failed*/

    expect(elemenToBeDisplayed.isPresent).toBe(true,'Write your custom       
         error message here');
   });
Optimworks
  • 2,537
  • 17
  • 20
31

Jasmine 3.3 includes withContext as the officially supported way to specify additional information about the expectation without worrying about which matcher you are using.

More info and examples: How can I add more information to matcher failure messages?

it('has multiple expectations with some context', function() {
  expect(munge()).withContext('munge').toEqual(1);
  expect(spindle()).withContext('spindle').toEqual(2);
  expect(frobnicate()).withContext('frobnicate').toEqual(3);
});
Gary Sheppard
  • 4,764
  • 3
  • 25
  • 35
HolgerJeromin
  • 2,201
  • 20
  • 21
  • 1
    Hey, @HolgerJeromin, I'm using jasmine 3.3.1, I write `expect(true).withContext("reason").toBe(false);` and I get `Failed: expect(...).withContext is not a function`. Any ideas why it is happening? – Sergey Pleshakov Apr 03 '19 at 18:20
  • 1
    @SergeyPleshakov I use Jasmine 3.3.0 with the expected result: `reason: Expected true to be false.` – HolgerJeromin Apr 05 '19 at 15:24
12

Since Jasmine 3.3, there's a way to do it through withContext

Example:

expect(someValue).withContext('expected someValue to be true...').toBe(true)

See also https://github.com/jasmine/jasmine/issues/641#issuecomment-457037665

Ernstjan Freriks
  • 622
  • 6
  • 12
  • 4
    Hey, @ernstjan, I'm using jasmine 3.3.1, I write `expect(true).withContext("reason").toBe(false);` and I get `Failed: expect(...).withContext is not a function`. Any ideas why it is happening? – Sergey Pleshakov Apr 03 '19 at 18:19
3

You can do it with fail() method.

it('should fail with a message', function() {    
  if (!foo) fail(`your message here`);
});
mrded
  • 4,674
  • 2
  • 34
  • 36
2

The other answers explain how to hack 'expect', but there is another approach that may solve your problem, though it requires you to flip your thinking around a little bit. Instead of thinking of the 'expect' as your behavior under test, think of all the expectations under a single 'it' call as your behavior under test.

The case where I've come across this problem the most is when I have a function that is doing some kind of intensive parsing and I want to write 20, nearly identical, tests.

Arrange your inputs and outputs like so:

var testDatas = [
  {
    input: 'stringtoparse1',
    output: 'String To Parse 1'
  },
  {
    input: 'stringtoparse2',
    output: 'String To Parse 2'
  },
  {
    input: 'stringtoparse3',
    output: 'String To Parse 3'
  },
];

Now iterate over the list of your test data, and call 'it' from inside the loop like so:

testDatas.forEach(function(test) {
  it('should parse for input ' + test.input, function() {
    expect(myParser(test.input).toEqual(test.output);
  });
});

You get to reduce the amount of extraneous code flying around your tests and you get to format a message for each expectation, or group of expectations.

codepuzzler
  • 134
  • 8
  • Wow, I wondered how a test runner would count the number of tests with something like this and found that with a for loop (using Chutzpah test runner and Jasmine in Visual Studio) that it added all iterations as individual tests to the Test Explorer! – Etherman May 19 '20 at 14:42
1

I had a requirement to log custom messages for Jasmine and I used the following method.

beforeEach(function(){
    this.addMatchers({
        customError: function(mesg){
                         this.message= function () {
                                           return mesg;
                                       };
                         return this.actual == true;
                         }
                     });
        });
if(<fail condidtion>){
    expect(false).customError(<Fail message>)
}

Please do note, what I have mentioned above is by jasmine 1 format. There will been slight change if you are using jasmine 2. Hope this is helpful for you

Murali Krishna
  • 171
  • 1
  • 11
1

This is what I'm using for Jasmine 2.6.4 with TypeScript (Jasmine+Chutzpah in Visual Studio).

The latest Jasmine version via NuGet seems to be 2.6.4 so I don't have the "withContext" thing (it also seems a bit of a clumsy way to do it, I prefer just to tag a message on the end of the matcher like many other frameworks).

Even though the "expectationFailOutput" parameter (the message to display) is present in the jasmine.d.ts typings it seems it it not officially supported by jasmine:

However, unofficially, it seems to work just fine for all except the toEqual matcher.

I use the following to add a new toBeEqualTo matcher globally which I copied from the original toEqual matcher and simply add the expectationFailOutput message to the end. The interface declaration bit lets us use expect(...).toBeEqualTo(...) without TypeScript complaining.

Example usage:

expect(x).toBe(y, "Some Message"); // stock message works with .toBe
expect(x).toEqual(y, "This is ignored"); // stock message ignored with .toEqual
expect(x).toBeEqualTo(y, "My message is displayed"); // new matcher added below

TypeScript implementation:

/// <reference path="../../Scripts/typings/jasmine/jasmine.d.ts"/>

declare namespace jasmine
{
    interface Matchers
    {
        toBeEqualTo(expected: any, expectationFailOutput?: any): boolean;
    }
}

beforeEach(function ()
{
    jasmine.addMatchers(<any>{
        toBeEqualTo: function (util, customEqualityTesters)
        {
            customEqualityTesters = customEqualityTesters || [];

            return {
                compare: function (actual, expected, expectationFailOutput)
                {
                    var diffBuilder = (<any>jasmine).DiffBuilder();

                    return {
                        pass: util.equals(actual, expected, customEqualityTesters, diffBuilder),

                        message: diffBuilder.getMessage() + ": " + expectationFailOutput
                    };
                }
            };
        }
    });
});
Etherman
  • 1,777
  • 1
  • 21
  • 34
  • What did you have in DiffBuilder? Protractor's jasmine does not recognise it. – Corné Aug 21 '20 at 11:28
  • 1
    No idea - I just copied code from the original .toEqual and added the fail message. For me it works in VS/Chutzpah. You may have a TypeScript type error - that's why I cast to otherwise TypeScript will say DiffBuilder not found. – Etherman Aug 24 '20 at 07:15
1

Example: I need to validate the color of the page while loading first time.

expect(obj.color).toBe(true, 10000, 'Custom Message');

in toBe:

  • true - Expected to be True,
  • 10000 - waitTime
  • Custom message(Based on our requirement we can write the msg/error in the log report)
0

On Jasmine 2.* a nice solution is to use the package jasmine2-custom-message. It allows a clean syntax like:

since(() => `${field[i].name} is expected to exist`)
  .expect(fields[i].element.exists()).toEqual(true);
Zac
  • 2,180
  • 2
  • 23
  • 36