Closed meeber closed 6 years ago
If this project was new, I'd feel like replacing all property assertions with method assertions would be the obvious correct choice. But given the maturity of Chai, I'm worried about how extreme of a breaking change it is. I'm currently undecided. If we do go with this approach, I'd say that it should not only be released in a new major version, but that it should be the only change introduced in the entire major version, both to make it easier for consumers to pick which version they want to use based on this one specific change, as well as to make it easier for consumers to update their code without having to worry about more than one change at a time.
Although they're not mutually exclusive, I'm only in favor of using ES6 Proxy to detect invalid assertions if we don't replace all property assertions with method assertions. It feels pretty cool and didn't end up being nearly as complex as I feared, but I'm a bit uncomfortable with having the code path be different based on the JavaScript version. Still, I think Problem A is a big enough problem that if we don't fix it by replacing all property assertions with method assertions, then we should fix it with ES6 proxy. I don't think Problem B is nearly as prevalent, so I'm not particularly concerned that this approach doesn't address it.
As for adding a mechanism to verify that the expected number of assertions were run in each test... I think this has a lot of potential as a cool feature, but will be difficult to do correctly as explained in my first post. I'm in favor of eventually working something like this into Chai core regardless of what we do with property assertions and ES6 Proxy. After all, it addresses one of the problems that neither of the other two approaches does.
In conclusion, I'm in favor of either replacing all property assertions with method assertions, or using ES6 Proxy to detect invalid assertions, but not both. Not sure which I prefer yet. Regardless, I'm also in favor of eventually adding a mechanism to verify that the expected number of assertions were run in each test, but I consider this a longer term addition.
Edit: One of the biggest advantages of replacing all property assertions with method assertions is that it allows consumers to use a linting tool to catch invalid assertions. But it's possible for a linting tool to provide that service anyway, without replacing property assertions. A custom ESLint plugin for Chai would likely be the best solution. Looks like there's already one in the works: eslint-plugin-chai-expect.
I think I'd like to get rid of property assertions - they have many different types of bugs, from odd stack traces, to confusing developers (both plugin developers and users writing tests). Also in the roadmap issue I briefly mentioned some of the impact these property assertions have:
Two of the biggest causes of fragmentation of chai users is due to low browser support, and the property assertions - which spawns off codebases like the following:
I think we can mitigate the pain of removing them by spreading the changes over several major versions, a loose plan would be:
import expect from "expect/methods"
, at the same time have import expect from "expect/old"
which loads the old one. import expect from "expect"
is an alias for "expect/old"
import expect from "expect"
aliases "expect/methods"
but we still keep "expect/old"
- users who want to keep the property assertions can just change 1 LOC (the import statement) and not have to change any of their test code"expect/method"
, we drop the "expect/old"
interface altogether.I'm on board for removing property assertions, and following a gradual approach in doing so, but in that case I think we should consider implementing the proxy stuff in the short term (even though that contradicts what I said earlier about not doing both). I think it'll be a decent chunk of time until #585 is in place, whereas I consider Problem A in my original post fairly urgent.
Ultimately I think all 3 solutions could be used. We've definitely had requests for counting assertions before (not necessarily because of property assertions though). But yes I agree, proxies will solve problems the quickest with (hopefully) the least impact.
+1
I was about to open the same issue right now.
Sometimes I mistype exist
as exits
or exists
without ever realizing, and too often I need to open the Chai documentation just to check whether NaN
is a valid property, whether it's called nan
or NaN
or similar stuff.
All the proposed solutions would work great for me. I'd have a further idea about the first solution: maybe the property assertions could return a noop function, rather than the object they're currently returning? This way former code can keep using them as property assertions, while new code could call them as functions to be sure the properties weren't misspelled. Not sure whether this would break the API though.
I'd have a further idea about the first solution: maybe the property assertions could return a noop function, rather than the object they're currently returning? This way former code can keep using them as property assertions, while new code could call them as functions to be sure the properties weren't misspelled. Not sure whether this would break the API though.
We did this before, around Chai 1.10.0. It didn't end well, because it causes all kinds of issues with chaining of assertions. You can read the full story here: https://github.com/chaijs/chai/issues/371#issuecomment-75033714. Here's the release notes for Chai 2.0 which removed this: https://github.com/chaijs/chai/releases/tag/2.0.0
We did this before, around Chai 1.10.0. It didn't end well, because it causes all kinds of issues with chaining of assertions.
What kinds of issues? If, instead of a noop, the assertion simply returns itself when called, wouldn't that cover those kinds of issues? If a proxy were used to check if an assertion is callable in the {{apply()}} trap (returning the assertion itself if not callable), perhaps it would be possible to "fix" existing plugins while at the same time retaining compatibility.
Property assertions frustrated me to the point of enforcing stricter linting rules for work projects, as several tests were passing when they should have failed due to typos or incorrect assumptions about the assertions available.
@andyearnshaw The good news is, starting with Chai 4.x.x, an error will be thrown when an invalid property assertion is used (due to type or incorrect assumption). See: #721
The best place to read about the issues with the previous attempt at allowing both property and method assertions is: #302, particularly https://github.com/chaijs/chai/issues/302#issuecomment-62540167
I think one could make an argument that it's worth supporting both property and method assertions if the only thing that breaks is assertions such as expect([1,2,3]).exist.length(3)
where
the .length
or .arguments
assertion immediately follows the chainable no-op, not allowing anything in between to return an assertion object instead of the no-op function. This may be more palatable than getting rid of property assertions altogether in favor of method assertions.
I'm not sure the idea you've proposed with proxies is viable. My understanding is that the Apply trap will only work if the thing being proxied is a function. In other words, it can't be used as a mechanism to detect if something that's being called is actually callable. Please correct me though if I'm misunderstanding your idea (or if I have incorrect knowledge of the proxy Apply trap).
@meeber: ah yes, it looks like you're correct. I recall an early implementation in Chrome allowed this, but now that I've checked the spec I can see that it's wrong.
Glad to see the throw for invalid property assertions coming in 4.x.x, that might change my mind about them!
Here's a trap I just fell in. Sorry if you're already aware of it.
myBool.should.equal.true;
This looks correct and makes intuitive sense, at least to me as a newcomer, but doesn't test anything.
Hi @tristanisme, thanks for your report!
The equal
assertion is only added as a method, the correct way of writing it should be myBool.should.equal(true)
or myBool.should.be.true
.
In your case I suppose your assertion is returning undefined
, because you are trying to access the property true
of the equal
method the Assertion
object has.
However, your syntax consideration seems common and natural enough for me to think it should be considered correct. IMO this behavior should be added to Chai's core. Perhaps we could make equal have a different behavior by defining its getter
function in order to enable our users to do this kind of assertion, but this feels a little weird to me, because the same word would have two different effects and this could end up confusing other users.
If we really wanted to do this I think it would only be a matter of using addChainableMethod
.
I'd like to hear @keithamus and @meeber opinions on this one, what do you guys think? Also, if you think this is going to be a long discussion we can have a separate issue to talk about it.
Proxies should solve this, come 4.0
- statements like that will throw an error.
As for supporting those, IMO I'd rather steer away from property assertions than into them.
@keithamus I just tested here using the 4.x.x branch and I don't think that Proxies would solve this case.
Even if he types myBool.should.equal.tue;
(missing the r).
The Invalid Chai property error will not be throwed because the equal property is not proxified. I did not digged into the code but I believe that only properties are being proxified.
What I did to test this was:
Checkout to the 4.x.x branch
Add the example to a random test
run npm test
And the tests passed
Please correct me if I'm wrong or if my method for testing this is wrong.
@vieiralucas we're in a bit of a mixed state right now. I have a vague feeling that Proxies might be in the master
branch, while the 4.x.x
branch contains other things. @meeber will probably have a better idea on this though.
@lucasfcosta and @vieiralucas are correct that proxies won't catch this particular problem. Since the equal
assertion is a plain method instead of a chainable method, .equal.true
and .equal.ture
are just accessing undefined properties on a regular function, so they silently do nothing.
It's also worth mentioning that something like expect(x).to.equal.true
is a more severe example of Problem B from my original post, because unlike expect(x).to.equal
, it looks like a valid assertion. This makes solving Problem B more urgent, nearly as urgent as solving Problem A with proxies was.
We can use proxies to wrap functions so we can mirror the same behaviour that we have with objects now, on the functions.
@keithamus Great idea. In fact, I wonder if an attempt to access any property on a non-chainable method assertion should throw an error, even if the property exists. For example expect(x).to.equal.length
could throw an error even though the function has a built-in .length
property.
Great idea by @keithamus, +1 for that!
Regarding @meeber's last example, I'm not sure it would be a good idea, since it would be kind of conflicting with the language's standard features. Even though we won't see many people doing that I think that your example would still be a valid construction and by doing it I (as an user) would expect to see the value of the length
property of the equal
function. However, I don't have a very strong opinion about it, let me know if you disagree or if you have any other ideas you'd like to share. 😄
The release of Chai v4 with proxy protection drastically mitigated this problem. Gonna close.
We have a problem in Chai. The problem is that it's too easy to write tests that appear valid but are actually broken in such a way that they pass no matter what. Strict TDD/BDD can help mitigate this problem, but it's not enough, especially when reviewing PRs from developers of varying skill levels.
Before discussing solutions, it's important to understand the different ways this problem can manifest. Here are three:
Commonly proposed solutions include:
None of these solutions are mutually exclusive. Here are some pros and cons of each approach:
Replace all property assertions with method assertions
Pros:
Cons:
Use ES6 Proxy to detect invalid assertions
Pros:
Cons:
Add a mechanism to verify that the expected number of assertions were run in each test
Pros:
Cons:
Note: A variation of this approach is to merely verify that at least one assertion was run in each test, instead of a specific number, which would be potentially easier for Chai to implement and developers to use, but would mean that it no longer completely fixes any of the problems, only decreases the likelihood of them occurring.