Skip to content
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

negated property(name, value) assertion #16

Closed
jfirebaugh opened this issue Feb 13, 2012 · 7 comments
Closed

negated property(name, value) assertion #16

jfirebaugh opened this issue Feb 13, 2012 · 7 comments

Comments

@jfirebaugh
Copy link
Member

I would expect a negated assertion to pass if and only if it would fail if not negated. But this is not true of the property(name, value) assertion:

expect('asd').to.have.property('foo', 3); // fails
expect('asd').not.to.have.property('foo', 3); // also fails (I would expect it to pass)

I realize the test verify the existing behavior, but is there any chance it could change?

@logicalparadox
Copy link
Member

You will notice the error type when this occurs is not an AssertionError, but a regular Error.. L569.

It didn't fail because the assertion failed, it failed because the pretenses of the test were insufficiently prepared and therefor outside of chai's ability to pass judgement. As such, I think the appropriate response is to inform the developer of this.

Make sense? Thoughts?

@jfirebaugh
Copy link
Member Author

As I see it, there are two equivalent interpretations of expect(o).to.have.property(p, v) and its negation:

  1. As an extension of expect(o).to.have.property(p), i.e. as a logical conjunction of two conditions: P ∧ P', where P = o[p] !== undefined and P' = o[p] === v. In which case the negation of this assertion, expect(o).not.to.have.property(p, v), should be defined by ¬ (P ∧ P'), or by De Morgan's, ¬P ∨ ¬P'. And since ¬P1 = o[p] === undefined is true if o does not possess property p, the negative assertion should pass.
  2. As simply o[p] === v. In which case the negation is o[p] !== v, which if o does not possess property p, is equivalent to undefined !== v, which also passes.

In neither case is chai unprepared to pass judgement as I see it. Maybe you can elaborate on your thinking?

@logicalparadox
Copy link
Member

Very logical arguments. I applaud your use of De Morgan's law! However, the point of the expect and should interfaces is to provide a natural language interface. I see that there are three possibly ways that your statement could be written in natural language...

expect('asd').to.not.have.property('foo', 3);
// 1. "I expect asd to not have the property `foo`, and assert that `foo` should equal 3."
// 2. "I expect asd to not have the property `foo`, and assert that `foo` should not equal 3."
// 3. "I expect asd to have the property `foo`, and assert that `foo` should not equal 3."
  1. This makes absolutely no sense. I cannot test the value of something I expected to not exist. Will always fail.
  2. This is your approach. You are negating both statements. Even if you logically get past the fact that I am testing the value of something I expected to not exist, foo will never equal 3, or anything for that matter. So why didn't you just expect('asd').to.not.have.property('foo') if you wanted to assert that the property doesn't exist? By your logic, will always pass.
  3. This is the current approach. Given what we have just seen, the logical assumption is that since a value of 3 was provide, foo was expected to exist. This will not pass as the expectation of having the property does not pass, or, stated...

It didn't fail because the assertion failed, it failed because the pretenses of the test were insufficiently prepared and therefor outside of chai's ability to pass judgement. As such, I think the appropriate response is to inform the developer of this.

@jfirebaugh
Copy link
Member Author

Actually, #2 is not my approach. My approach is: "I expect that either asd does not have property foo, or that it does, but not with the value 3." That's the logical negation.

I believe it is least surprising if the assertion "X should Y" succeeds exactly when the assertion "X should not Y" fails, and vice versa. It's simple, predictable, and how negation works in every assertion library I've seen prior to chai. Though now I've looked and it seems should.js and expect.js both have this quirk as well, so perhaps I'm on the losing side in that regard.

As for the third interpretation, I would write such an assertion as expect('asd'.foo).not.to.equal(3). It's more succinct and leaves no room for misinterpretation.

@logicalparadox
Copy link
Member

I'm not sure I like 'either' as an approach in this testing scenario. I don't like that a single assertion could pass for two separate scenarios. Especially where there is multiple interpretations of how the language chain should operate, Its better to throw and force the user be more succinct.

A hazard of working with language chaining assertion libraries... but perhaps it's a curtesy in the long run.

@logicalparadox
Copy link
Member

I am going to close this issue. At this time, compatibility with similar libraries (expect/should) makes chai more accessible to new users wanting to migrate.

As far as the debate of logics go, shall we call it a stalemate for the time being? ;)

@hurrymaplelad
Copy link
Contributor

👍 I'm struggling to write a .to.redirectTo(url) assertion that makes sense.

Ideally, expect({ statusCode: 301, headers: { location: 'foo' }}).to.redirectTo('foo') and expect({ statusCode: 200 }).not.to.redirectTo('http://example.com') should both pass.

I'm attempting to compose this assertion from chai-http status and header assertions, but having lots of trouble De Morganizing those sub-assertions. For now I've settled for expect({ statusCode: 200 }).not.to.redirectTo('http://example.com') failing. Seems in line with this discussion but unsatisfying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants