Testing For Equality In JavaScript
Posted on october 20th, 2008
This article is about the function that drives the same test assertion in the QUnit JavaScript test framework. The function is called equiv and it has been created for test suites purposes. It is mostly useful for JavaScript tests but it can be also very useful in other circumstances because it is very powerful.
Hope this place can be a resource and a platform for improovement of such code. May it be for the manner it should be used in QUnit also. This article, the examples and the code on that page will evolve with time as equiv change and gets better. Help is welcome.
What does equiv do?
It compares two ore more values together and returns true when they are all equivalent, false otherwise. It is able to work on any JavaScript types, even when they are compared together, without returning unexpected results. It knows how to deals with specific bugs, or call it deficiencies in the language implementation. Of course it traverses recursive structure, it knows how to deal with references and it tries to be the more efficient and performant as possible. It's free, well documented and well tested. You'll find everything in the download page.
About equiv
equiv is the service function behind the QUnit same assertion API. It has been implemented to behave the most naturally possible about comparing "things" together.
About it's name
"equiv" is short for "equivalent". I don't think it should be call "equals" if we want to be precise for the following reasons.
- It goes beyond object references and compare content by inspecting them.
- Closures makes that some content is lock forever and not accessible, so it is impossible to know at which point objects are really equals.
- Because functions can be values (e.g. anonymous function) and there is no way to compare them but by their references.
Input Supported
undefined null NaN Infinity -Infinity Boolean Number String Date RegExp Array Object Function*
* With some limitations.
How does it deals with functions?
Earlier version of equiv were skipping function. Now it does both skipping and aborting following some rules.
Same references will always be considered equals whatever happens (naturally).
Abort when in an object literal*. Abort when not a property value.
*instances of the Object constructor.Skip functions that are instances of any contructor but the Object constructor.
The rules does apply in recursive structure and behave as expected.
Language deficiencies and traps to be aware of
Avoiding Unexpected Results
Because testing is something serious, you cannot assume anything about the code you are testing right? Not even that a function that return a boolean will always return a boolean. What if you have you missed somewhere the return statement? What if your all your tests passed when your function return undefined instead?. It would be a bad thing.
In JavaScript particularly you have to be very careful when comparing "things" together because there is some traps that you could easily fall in (see above). Just as an example comparing {} with [] is not quite simple. A lot of code that you may find comparing objects and/or arrays could failed this one.
Let's see how the code of a function that expect only objects* could be error prone:
*By objects I mean the object reprented by the literal {} and key: value pairs.
The problem for the first 2 tests (line #21, #22) is that none of null, {} or [] doesn't have any properties, so they are considered equal because no difference were found when comparing them. Same for comparing number (line #30) or boolean (line #31) which each doesn't have any properties. Comparing strings though behave correctly because strings can be accessed with the subscript notation of it's index (e.g. as an array).
But extending the array's prototype(line #27) with a dummy function "foobar" was enough to make [] differs from {} because the object's prototype didn't hold the "foobar" property.
The code above is just bad for testing suites purposes because it can't deals with unexpected type values which is unacceptable for a test framework.
QUnit assertions deprecation warning
ok is expecting a JavaScript truthy value or expression. It's quite simple and it gives enough flexibility. It isn't error prone unless it is used by someone who doesn't understand well truthy VS falsy JavaScript expression e.g. it would behave differently for "" and [], for 1 and 0. It has the disadvantage not to be precise and though makes the tests less efficient. You have to know what you are doing.
equals compares booleans numbers and strings together. Unfortunately it isn't strict on type checking. It is error prone if you pass unexpected types as arguments.
Assumptions is error prone
In my opinion, assuming certains type of values when writing test suites isn't the way to ensures robust tests. Sometimes some application's functions can return different type of data depending on the input. Or sometimes, a return statement may have been omitted, leaving the default return value to undefined. This is where equals fails when comparing null with undefined, returning true instead of false.
same can replace ok, equals and all other QUnit test assertions
same (based on equiv) being more precise than any other precedent QUnit assertions can be use to replace them. ok being different, should be use if you know what you are doing and you don't need precision when expecting truthy expressions. For the reasons I've already mentioned, those assertions should be deprecated: equals, compare(already deprecated), compare2(already deprecated), isSet and isObject.
The code
hoozit.js: needed to determine the type of a value
equiv.js
About arguments
- when < 2 : return true. There is nothing to compare with.
- when 2 : return true if 1st is equivalent to 2nd
-
when > 2 : return true if
- 1st is equivalent to 2nd and ...
- 2nd is equivalent to 3rd and ...
- 3rd is equivalent to 4th ... and so on.
Open up the testrunner in a new page. Visit the download page.
Examples of use
Most common use
Multiple arguments: using apply and the array argument
Multiple arguments: QUnit integration
For now same only allows the comparison of 2 arguments. It would be nice if we could use multiple arguments. Because of the last optional message argument of same, it isn't possible. But another assertion should support it. It could be call "allSame".
Add allSame to the testrunner
or download testrunner.js using allSame
allSame examples of use
Improvements
Do you consider that having a negative version of same and allSame could be useful?
If you feel like equiv could be better share your ideas or solutions in the comments form.
You will find in the download page, the sources, the test suites, and the QUnit test runner source (including allSame).
Updated on october 30th, 2008

Leave a comment