Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

not.contain.keys(XX).and.contain.keys(YY) does not work as expected #256

Closed
andimeier opened this Issue · 3 comments

4 participants

Andi Meier Charlie Rudolph Arnaud Benhamdine Keith Cirkel
Andi Meier

I want to test this:

expect(result).to.not.contain.key('error');
expect(result).to.contain.key('info');

Let's assume a "green" case (meets the test criteria):

result = {
  info: 'Worked'
}

This works. If I combine both asserts into the single-line version:

expect(result).contain.key('info').and.not.contain.key('error');

it is still working.

But if swap both asserts and write it like this:

expect(result).to.not.contain.key('error').and.contain.key('info');

then I would assume that the test is still green but it tells me that:

Uncaught AssertionError: expected { Object (data, info) } to not contain key 'info'

Apparently the not keyword is still "in use" after the .and.. This seems counterintuitive to me. I think the .and. keyword should be used to chain separate assumptions together independently from each other. So my opinion is that the .and. keyword should "reset" any modifiers like ".not.".

What do you think?

BTW: a simpler case:

expect(1).to.equal(1).and.not.equal(2);
expect(1).to.not.equal(2).and.equal(1);

The first combination would yield "OK" whereas the second line would fail.

Charlie Rudolph

I'm not certain about resetting any modifiers but I like the idea that and resets the not modifier.

Arnaud Benhamdine

Had the same question : I paste a previous answer by @logicalparadox :

I think the misunderstanding comes from your expectation that Chai follows most/all English grammar rules. Unfortunately, English grammar has way too many rules (and exceptions to those rules) for it to be a reasonable undertaking to implement.

The challenge with designing Chai's chain-able assertions is finding the balance between being expressive and concise. Even if full grammar wasn't a daunting task to implement and document, it would make the API less concise, which is not good for a testing environment.

RULE: Any "flag" which modifies the behavior of an assertion (negation not or inclusion include/contain, etc...), once set to true should remain true until the end of the chain.
This effectively means that when concatenating multiple assertions into a single chain you should always put those assertions which you expect to be true before those you want to negate.

expect('foo').to.be.a('string').and.not.empty;
expect([ 'bar' ]).to.be.an('array').and.not.empty;

Also, one thing to note is that to, be, and, in addition to a few others are convenience noop() chains. As their only goal is to aid us humans, they can be omitted without side-effect.

/// same as above
expect('foo').a('string').not.empty;
expect([ 'bar' ]).an('array').not.empty;

In short, the behavior you are observing is intended, but once you get used to Chai's rudimentary grammar rules you will find that the power of concise wins over the convenience of expressive, especially when testing.

Keith Cirkel
Collaborator

Thanks for the issue @andimeier, and feel free to keep them coming, but in this case @abenhamdine has summed this up nicely. I'm going to close this, but if you have any other thoughts on it feel free to add them here.

Keith Cirkel keithamus closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.