New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Human-readable context for expectations #1965

Closed
timoxley opened this Issue Oct 21, 2016 · 69 comments

Comments

Projects
None yet
@timoxley

timoxley commented Oct 21, 2016

If there's multiple expectations in a single it , currently it appears to be impossible to figure out which expectation actually failed without cross-referencing the failure with line numbers in your code.

test('api works', () => {
    expect(api()).toEqual([]) // api without magic provides no items
    expect(api(0)).toEqual([]) // api with zero magic also provides no items
    expect(api(true)).toEqual([1,2,3]) // api with magic enabled provides all items
})

Which expectation failed? The first or the second?

image

It would be nice if there were some human readable context that made it immediately clear which expectation failed and what the expectation output actually means in human terms, without having to find the line number at the top of the stack trace and mapping that back to the code.


Compare the tape equivalent below. Ignore that tape doesn't bail after the first assertion failure.tape prints out a human-readable message above each expectation failure, allowing you to know exactly which test failed without going back to the test file.

Note this also pushes the human-readable noise off to the end of line in the test source, where you might write a comment anyway.

test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
})

image


It seems the only way to attach human-readable information to errors with jest is to wrap everything in an additional it which is unnecessarily verbose IMO.

describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Ideally, one could attach some human-readable context onto the end of the expect somehow.

e.g.

Context message as additional optional parameter for assertion methods:

test('api works', () => {
    expect(api()).toEqual([], 'api without magic provides no items')
    expect(api(0)).toEqual([], 'api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3], 'api with magic enabled provides all items')
})


Or context message as a chained .because or .why or .comment or .t or something:

test('api works', () => {
    expect(api()).toEqual([]).because('api without magic provides no items')
    expect(api(0)).toEqual([]).because('api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3]).because('api with magic enabled provides all items')
})

Alternatively, it'd be even better perhaps if jest could simply read the file and print the actual source code line that expectation itself is on.

@cpojer

This comment has been minimized.

Contributor

cpojer commented Oct 24, 2016

Hey! So we actually used to have this in Jasmine but found that over thousands of test files at FB, nobody used it. So for now we are printing a nice error message with approximate information and a stack trace that will lead to the expectation (just like in your screenshot). I agree we could print the line that throws but quite often the assertion is multiple lines long:

expect(a).toEqual({
  …
});

so this wouldn't actually look so good and we'd have to use a parser to parse the JS and extract the relevant info (and collapse long lines) or something similar to make it pretty.

Personally I think we are showing enough information for now but happy to reconsider. If you have ideas for something that isn't very complex but adds more context which helps resolve issues faster, let me know.

@timoxley

This comment has been minimized.

timoxley commented Oct 24, 2016

we actually used to have this in Jasmine but found that over thousands of test files at FB, nobody used it

@cpojer so the pattern is to wrap each assertion in an it? and/or just trust in the line numbers?

Is it possible that this pattern has been adopted less because it's better or worse, but more just for consistency with the existing tests? or perhaps not knowing the feature exists? I didn't know this was in Jasmine.

I agree we could print the line that throws but quite often the assertion is multiple lines long

Refactoring to a single line could encourage more semantic information in the assertion? Perhaps?

const adminUser = {
  …
}
expect(a).toEqual(adminUser);

Personally I think we are showing enough information for now but happy to reconsider

The example above shows that it's difficult to discover exactly which assertion failed unless you add verbose (IMO) wrappers around everything. This is especially true in a transpiled environment where sourcemap line numbers aren't always accurate. I believe that quickly and accurately understanding broke and where is important, as are concise tests.

If you have ideas for something that isn't very complex but adds more context which helps resolve issues faster, let me know.

I made a few suggestions above:

Are you looking for something simpler, or different?

@aaronabramov

This comment has been minimized.

Member

aaronabramov commented Oct 27, 2016

hey @timoxley! we already thought about adding something like this.

so the issue with the first option is that some matchers have optional arguments, and that makes things more complicated.

e.g. here on the second case we won't know if the argument is a proximity or an error message

expect(555).toBeCloseTo(111, 2, 'reason why');
expect(555).toBeCloseTo(111, 'reason why');

second suggestion won't work because the matcher will throw as soon as something does'n meet expectation

expect(1).toBe(2)/* will throw here */.because('reason');

we could attach the reason before the matcher executes, like this:

expect(1).because('reason').toBe(2);
// or 
because('reason').expect(1).toBe(2);

but this API doesn't really look that good.

another option would be to add a second arg to expect

expect(1, 'just because').toBe(2);

but it is pretty much the same as the previous option.

@cpojer

This comment has been minimized.

Contributor

cpojer commented Oct 27, 2016

The reason why I think this isn't very useful is because engineers don't want to waste time writing tests. Anything we do to make it harder for them will just lead to worse tests.

In the past, the best solution was actually to create custom matchers. We'll introduce expect.extend in the next version of Jest and it will allow you to easily create matchers like:

expect(a).toEqualMySpecificThing(…)

which should allow you to write more expressive failure messages. I've seen this used a lot in projects like Relay. See all the matchers: https://github.com/facebook/relay/blob/master/src/tools/__mocks__/RelayTestUtils.js#L281

@cpojer

This comment has been minimized.

Contributor

cpojer commented Nov 14, 2016

Closing due to inactivity but happy to reopen if there are good ideas.

@cpojer cpojer closed this Nov 14, 2016

@timoxley

This comment has been minimized.

timoxley commented Mar 2, 2017

@cpojer @dmitriiabramov apologies for delay.

A second arg to expect or chaining a reason with .because would be great. What needs to be done to make this happen or not?

@timoxley

This comment has been minimized.

timoxley commented Mar 2, 2017

engineers don't want to waste time writing tests

@cpojer Agreed! In addition to not wanting to waste time debugging tests, this is exactly why I believe a less verbose API with more failure context would be preferable.

For some concrete numbers, using the simple example from my comment above, to get equivalent assertions + context with tape, Jest requires the programmer write nearly double the amount of ceremonial boilerplate:

  • 1.8x the lines (6 vs 11)
  • 2x the indentation (1 vs 2)
  • 2x the parens/curlies (24 vs 48) !

This could be improved!

// tape
test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
  t.end()
})

// jest
describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Update: I suppose you could write the jest tests on a single line with arrows:

// jest
describe('api works', () => {
  test('api without magic provides no items', () => expect(api()).toEqual([]))
  test('api with zero magic also provides no items', () => expect(api(0)).toEqual([]))
  test('api with magic enabled provides all items', () => expect(api(true)).toEqual([1,2,3]))
})

This makes for some longer lines, but does improve the stats we compared earlier somewhat:

  • 0.8x the lines (6 vs 5)
  • 1x the indentation (1 vs 1)
  • 1.75x the parens/curlies (24 vs 42)

However I think having the test description at the start of the line, without a linebreak, makes it harder to visually parse the logic because it puts the "meat" of the test, i.e. the actual assertions, at some arbitrary column position.

Parsing the code is more important than reading the test description, which is basically just a glorified comment. This is why nobody writes comments at the start of the line .e.g. this would be sadomasochistic madness:

/* api without magic provides no items */ expect(api()).toEqual([])
/* api with zero magic also provides no items */ expect(api(0)).toEqual([])
/* api with magic enabled provides all items */ expect(api(true)).toEqual([1,2,3])

Ideally all the assertion code would line up neatly in the same column so it's easily parsed by a human. Based on this thinking, I'd strongly opt for the trailing .because form rather than alternative suggestion of a second argument to expect.

@cpojer

This comment has been minimized.

Contributor

cpojer commented Mar 2, 2017

Thanks for keeping the conversation going. Note, that you also don't need the describe block, further making things smaller. .because won't work unfortunately because when the matcher throws (which happens before .because is called), we won't have a way to extract the name.

@timoxley

This comment has been minimized.

timoxley commented Mar 2, 2017

Use the last arg of each matcher function then?

@aaronabramov

This comment has been minimized.

Member

aaronabramov commented Mar 4, 2017

It would only work if we add it as a second arg of expect.
We can't add it as a the last arg for every matcher function because of ambiguity
e.g.

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');
@jayphelps

This comment has been minimized.

Contributor

jayphelps commented Mar 19, 2017

Closing due to inactivity but happy to reopen if there are good ideas.

@cpojer it's unclear if the jasmine (and others) way of expect(value).toBe(something, 'because message') has been ruled out?

One example is testing redux-saga/redux-observable stuff where you're testing a state machine. It's very helpful to have a descriptive message about at what state did it fail. That example is contrived so the descriptions are as well, though..

@aaronabramov

This comment has been minimized.

Member

aaronabramov commented Mar 20, 2017

@jayphelps we're not using the jasmine way any more since we rewrote all jasmine matchers

@jayphelps

This comment has been minimized.

Contributor

jayphelps commented Mar 20, 2017

@dmitriiabramov sorry my question wasn't clear. Has the jasmine way of doing it been ruled it to be added back? Doing the same thing they allow.

@aaronabramov

This comment has been minimized.

Member

aaronabramov commented Mar 20, 2017

@jayphelps as i said before, it won't work for all matchers because of the ambiguity.

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

and sing jest matchers can be extended with a third party packages i don't think it's a good idea to mess with the argument list

@aaronabramov

This comment has been minimized.

Member

aaronabramov commented Mar 20, 2017

the cleanest option is probably to have it as a second argument of expect, since it always takes exactly one argument.

expect(123, 'jest because').toEqual(123);

i'm not sure if we want to overload the API though. We almost never used it in facebook test suites, and for special cases i think it'm easier to just define a new test:

beforeEach(someSharedSetup);
test('reason or description', () => expect(1).toBe(1));

it's just a few lines more :)

@thymikee

This comment has been minimized.

Collaborator

thymikee commented Mar 20, 2017

Or you can even put it into another describe() call.

@jayphelps

This comment has been minimized.

Contributor

jayphelps commented Mar 20, 2017

@dmitriiabramov The annoying cases are when you build up state, like in a state machine for sagas, epics, etc. Each test requires the previous state changes, isolating them requires a ton of duplication without any gain AFAIK.

it('stuff', () => {
  const generator = incrementAsync();

  expect(generator.next().value).toBe(
    call(delay, 1000)
  );

  expect(generator.next().value).toBe(
    put({ type: 'INCREMENT' })
  );

  expect(generator.next()).toBe(
    { done: true, value: undefined }
  );
});

Or you can even put it into another describe() call.

Can you elaborate this? Nesting describe calls AFAIK was just for dividing section titles, tests are still run concurrently right?

@thymikee

This comment has been minimized.

Collaborator

thymikee commented Mar 20, 2017

Test suites (files) run concurrently, test() calls don't.

@aaronabramov

This comment has been minimized.

Member

aaronabramov commented Mar 20, 2017

i'm starting to think that something like

test('111' () => {
  jest.debug('write something only if it fails');
  expect(1).toBe(2);
});

can be a thing

@franciscop

This comment has been minimized.

franciscop commented May 28, 2017

From this discussion and this repository I think a nice and semantic one would be:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

The usage is similar to the because one, but it just makes more of sense semantically.

If you like this I might be able to work out a PR adding the since functionality.

@nylen

This comment has been minimized.

nylen commented Jul 7, 2017

Please implement an easy way to do this. I don't use it very often, but especially for more complicated tests, it's helpful to know exactly what is failing without having to go digging.

Please don't say "rewrite your test suites to be simpler". The only thing engineers hate more than writing test suites is rewriting test suites.

Another proposal that I've seen somewhere, I forget where It was in this very same issue, with an explanation of why it wouldn't work. I should probably get some sleep :)

@franciscop

This comment has been minimized.

franciscop commented Jul 8, 2017

I got some simple "prototype" demo working, I would need to implement the recursion now. It is a thin wrapper using Proxies around the global variables and then over each method. However, Proxies are not supported by older browsers and cannot be polyfilled so it might not be acceptable for Jest. This is the general structure for the wrapper:

const since = (text) => {
  return new Proxy(global, {
    get: (orig, key) => {
      return (...args) => {
        try {
          const stack = orig[key](...args);
          return new Proxy(stack, {
            get: (orig, key) => {
              return (...args) => {
                try {
                  const ret = orig[key](...args);

                  // ... implement recursion here

                } catch (err) {
                  console.log('2', key, text, err);
                  throw err;
                }
              }
            }
          });
        } catch (err) {
          console.log('1', key, text, err);
          throw err;
        }
      };
    }
  });
};

There are three realistic options:

  • This way is acceptable so it should be added to the main Jest library. I clean it up and create a PR.
  • Dig deeper into Jest and modify the core library. A lot of work, so wouldn't do anything until some member(s) say something semi-oficially on this issue/direction.
  • Finish it this way and publish it as a package. Undesirable since it's not easily discoverable.

Edit: see it in action:

describe('Test', () => {
  it('works', () => {
    since('It fails!').expect('a').toEqual('b');
  });
});
@spontaliku-softaria

This comment has been minimized.

spontaliku-softaria commented Aug 30, 2017

You need expectation context to make test results sane when you have non-trivial tests. Real-world tests wouldn't always be that simple.

Remember custom matchers - they hide mathing complexity. But when test fails, hiding this complexity is not what you want becase you want maximum info about failure. Expectation context alows you to provide this context manually. Not ideal I guess, some kind of automatic context would be better, but it is the only way I've seen by now.

When I broke something and it fails, with Jest I have to debug it manually or add logging or whatever modifications. Which is much less convenient than just look at test run results.
In Jasmine for example, we have an ability to print some context to make more sense about failure.
In Java's most popular test framework JUnit we have exact same feature too.

Sorry if I'm mistaken, but I don't see any technological counter-arguments to this feature here. And things like "this shouldn't be added because it won't look good" are just ridiculous.

@gaearon

This comment has been minimized.

Member

gaearon commented Sep 15, 2017

Can we reopen? Even jest.debug() as suggested by @aaronabramov above would be helpful to me.

@ZacharyRSmith

This comment has been minimized.

ZacharyRSmith commented Sep 21, 2017

This:

it('has all the methods', () => {
	since('cookie is a method', () => expect(reply.cookie).toBeDefined());
});

can be supported by adding this:


// setupTestFrameworkScriptFile.js
// http://facebook.github.io/jest/docs/configuration.html#setuptestframeworkscriptfile-string
global.since = (explanation, fn) => {
	try {
		fn();
	} catch(e) {
		e.message = explanation + '\n' + e.message;
		throw e;
	}
};

@ZacharyRSmith

This comment has been minimized.

ZacharyRSmith commented Sep 22, 2017

Also, jasmine-custom-message looks similar to what's requested:

describe('test', function() {
  it('should be ok', function() {
    since(function() {
      return {'tiger': 'kitty'};
    }).
    expect(3).toEqual(4); // => '{"tiger":"kitty"}'
  });
});
@sharikovvladislav

This comment has been minimized.

sharikovvladislav commented Jan 29, 2018

@SimenB @mpseidel what is assert? is it some third party library? I can't find anything in jest docs.

@mpseidel

This comment has been minimized.

mpseidel commented Jan 29, 2018

@sharikovvladislav assert is a node core module https://nodejs.org/api/assert.html

@sharikovvladislav

This comment has been minimized.

sharikovvladislav commented Jan 29, 2018

@mpseidel oops! I didn't know. Thank you. It works.

@beattyml1

This comment has been minimized.

beattyml1 commented Feb 21, 2018

I'm using the following code fragment to hack around this limitation of the framework (in TypeScript but just remove the type annotations for JS)

export const explain = (expectation: () => void, explanation: string) => {
    try {
        expectation();
    } catch(e) {
        console.log(explanation)
        throw e;
    }
}
@eric-burel

This comment has been minimized.

eric-burel commented Mar 14, 2018

Hi,
I am surprised that nobody mentioned loops yet. The message would not just be a string, but a dynamic string depending on the loop iteration.
jest-plugin-context is nice, thanks for this works, but it is a bit heavy and the initial issue is still relevant imo.
Look at this test

describe('MyStuff', () => {
    it('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];
      expectedKeys.forEach((key) => {
        expect(wrapper.find({ id: key }).length).toEqual(1);
      });
    });
  });

Good luck finding your culprit. Right now I must add a line or test an object instead like {len:.., key:..}, this is not clean and not user-friendly.
I think this use case is relevant, for forms and item rendering check for example.
The syntax could be as simple as toEqual(1).context("my message") or toEqual(1, "my message") (though of course I know that implementation is always harder, and I respect the great job you did with Jest).

@tarjei

This comment has been minimized.

tarjei commented Apr 4, 2018

Maybe use the same format as chai does - i.e. add the message as a second argument to the expect call:

expect(foo, 'this detail').toEqual(2)

T

@akkerman

This comment has been minimized.

akkerman commented Apr 18, 2018

Used jasmine before, so came here to find it's not supported.
However afik these things are all functions. Can we not just do something like:

describe('MyStuff', () => {
    describe('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];

      expectedKeys.forEach((key) => {
        it(`contains key "${key}"`, () =>
          expect(wrapper.find({ id: key }).length).toEqual(1)
        )
      })
  });
});

2018-04-18-222246_646x390_scrot

@eric-burel

This comment has been minimized.

eric-burel commented Apr 19, 2018

@akkerman Nice solution. Since describe and it are magic globals provided by jest I must admit that they can feel obscure, I was not sure writing ìt` in a loop could work.

@geovanisouza92

This comment has been minimized.

geovanisouza92 commented Apr 19, 2018

What about chaining another modifier?

expect(foo).toEqual(bar).because('reason with %s placeholders')

Or maybe a function

expect(foo).toEqual(bar).explainedBy((result) => `Lorem ipsum ${result}`)
@tarjei

This comment has been minimized.

tarjei commented Apr 19, 2018

@SimenB

This comment has been minimized.

Collaborator

SimenB commented Apr 19, 2018

The way expect work is by throwing, so that wouldn't work anyways.

I think either expect(something, 'some helpful text on failure').toEqual(somethingElse) or expect.context(something, 'some helpful text on).toEqual(somethingElse) are the best alternatives, but I don't really like either of them

@callumlocke

This comment has been minimized.

callumlocke commented Jun 25, 2018

Can this be reopened? It seems Jest still has no good solution for testing how state changes over multiple interactions, for example:

  • testing how a stateful React container changes in response to a series of events
  • testing how a web page changes throughout multiple interactions using Puppeteer

Both of these cases require performing a series of actions, and asserting how the state changed (or didn't change) after each action, so multi-assertion tests are sometimes necessary. It's not always possible to solve this kind of problem with beforeEach.

@kentcdodds

This comment has been minimized.

Contributor

kentcdodds commented Jun 25, 2018

I keep finding situations where this would be really useful. Specifically when I'm running multiple interactions and assertions as @callumlocke explains.

If we come up with an API that folks don't hate, is this something you'd be willing to pursue? I really think this would be a valuable and much used feature.

@rickhanlonii

This comment has been minimized.

Member

rickhanlonii commented Jun 26, 2018

Here's a summary of the proposed solutions:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

expect(api(), 'api without magic provides no items').toEqual([])
expect(api()).because('api without magic provides no items').toEqual([])
since('api without magic provides no items').expect(api()).toEqual([]))
because('api without magic provides no items').expect(api()).toEqual([]))
jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

Note that a trailing .because() is not possible, so not included as an option. Same for a trailing arg in each matcher.

All four options in the first group are supported today. Personally, I find that the first option (a code frame with a comment) works great. And even better than that is using a custom matcher (option 4).

I think what's we need to understand to make movement on this is: what's more appealing about the options in the second group than the options in the first? What does the second group add which can justify core maintenance for all of the matchers we provide (across async matchers, asymmetric matchers, spy matchers, throw matchers, promise matchers, and custom matchers)?

@eric-burel

This comment has been minimized.

eric-burel commented Jun 26, 2018

Hi,
You basically got a few use cases:

  • Multiple assertions tests (you need to assert multiple things in a test)
  • Dynamically generated assertion context (you want a variable in your failure message to make it clearer, e.g for printing a specific field of your failing object because you got a lot of tests)
  • Dynamically generated assertions (you make a loop that generate assertions)

First group options are mainly meant for the first use case. If you encounter the need for dynamically generated assertion, as proposed, you can nest calls to it and test, so that a test can generate new tests in a loop. The problem is that you generate tests and not assertions. Imagine that I want to assert something on each element of a 1000 elements array, this will bloat the test summaries.

Since those dynamic use cases are still rare though, imo we should stick for the solution that requires minimal work for the maintainers. I personally like the because/since solution, because it sounds quite simple. I guess the implementation would be mostly wrapping expect in a try/catch that prints the message and returning it?
jest.debug sounds weird, to me debugging is printing a message even if the tests actually passes
The "last argument" option is also good but I am not sure if it is doable since expect can accept a variable number of arguments?

@kentcdodds

This comment has been minimized.

Contributor

kentcdodds commented Jun 26, 2018

I'm fine with any option. I just want the feature. I'm not huge on the jest.debug API either, but if it's the only one that makes sense I'm fine with it because I just want this feature.

@rickhanlonii

This comment has been minimized.

Member

rickhanlonii commented Jun 26, 2018

@kentcdodds what about the four existing options?

@eric-burel have you seen test.each and describe.each added in Jest 23 (available as a standalone for Jest <23)?

@kentcdodds

This comment has been minimized.

Contributor

kentcdodds commented Jun 26, 2018

Like I said, I don't really care which option we go with. I just want the feature to exist. I guess if I were to sort them by order of preference it would be:

  1. expect(api(), 'api without magic provides no items').toEqual([])
  2. because('api without magic provides no items').expect(api()).toEqual([]))
  3. since('api without magic provides no items').expect(api()).toEqual([]))
  4. expect(api()).because('api without magic provides no items').toEqual([])
  5. jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

(test|describe).each is great, but doesn't solve the issue where you want to have multiple actions/assertions in a single test.

@rickhanlonii

This comment has been minimized.

Member

rickhanlonii commented Jun 26, 2018

The feature does exist today with four options:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

What is wrong with these? The proposed new solutions only seem to be marginally better than these existing solutions. What benefits do they bring over what we have that justify the maintenance cost?

@eric-burel

This comment has been minimized.

eric-burel commented Jun 26, 2018

@rickhanlonii Nice I did not know about test.each, that's truly a great feature, thanks for pointing this out. I think it solves the issue for my 3rd use case, dynamically generated test from an array.

So it left the second I listed: having a dynamically generate failure message, which would make debugging faster. I don't have much use cases right now, maybe when you test an object field value, you would like to print the whole object on failure. That's legitimate imo, as anything that makes writing test easier, even if marginal or a bit redundant. After all we can both write it and test, unless there is a difference I don't know about this is mostly for comfort.
It is marginal but really something expected (no pun) by users, as this thread shows.

Edit: creating a test in another test with it or test and a dynamically generated name for the test is a valid solution but I really don't like creating a test when I mean creating an assertion. I would never have guessed it was possible if the solution hasn't been given in this thread.

@cognivator

This comment has been minimized.

cognivator commented Aug 1, 2018

This is crazy. Just add a second, optional parameter to expect(). Those of us who want to use it will (selectively), and those who don't, won't.

Mocha has been doing this forever... it's one of the reasons I abandoned Jasmine years ago (the other being much better timer mocking.) If I weren't having to join the React bandwagon, I wouldn't be using Jest or any other Jasmine derivative.

@johnpmitsch

This comment has been minimized.

johnpmitsch commented Aug 2, 2018

Printing out a message on error is a convention in so many other testing frameworks and I was surprised to not see it in Jest. I've found a lot of helpful examples in this thread (thank you for those), but adding an explicit way to print a custom error on test failure would be a nice addition to Jest's usability. This would make it easier for developers who are used to other testing frameworks (including non-JS ones) to ramp up on Jest.

@SimenB

This comment has been minimized.

Collaborator

SimenB commented Aug 2, 2018

@mattphillips do you think it's possible to do something similar to jest-chain here to allow a solution to exist in userland? E.g. second argument to expect

@talbronfer

This comment has been minimized.

talbronfer commented Aug 7, 2018

Honestly, this is something very standard in most JS testing frameworks. Very disappointed not to find it in Jest as we write all of our tests with a custom error message.

@mattphillips

This comment has been minimized.

Collaborator

mattphillips commented Aug 7, 2018

@SimenB sorry I only noticed your message this morning!

Yes this is doable in userland, I've just knocked it up and released it as jest-expect-message https://github.com/mattphillips/jest-expect-message

Feedback welcome 😄

@SimenB

This comment has been minimized.

Collaborator

SimenB commented Aug 7, 2018

Awesome, thanks for doing it!

@smartin015 smartin015 referenced this issue Sep 3, 2018

Merged

Karma -> Jest #441

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment