Bug 1688669 - Add a helper to assert property values in objects r=canaltinova

Differential Revision: https://phabricator.services.mozilla.com/D102939
This commit is contained in:
Julien Wajsberg 2021-02-15 20:32:01 +00:00
Родитель 761a4721d8
Коммит fb405c1684
3 изменённых файлов: 338 добавлений и 0 удалений

Просмотреть файл

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals Assert */
/**
* This file contains utilities that can be shared between xpcshell tests and mochitests.
*/
@ -218,3 +220,175 @@ function getSchema(profile, name) {
}
throw new Error(`Could not find a schema for "${name}".`);
}
/** ------ Assertions helper ------ */
/**
* This assert helper function makes it easy to check a lot of properties in an
* object. We augment Assert.jsm to make it easier to use.
*/
Object.assign(Assert, {
/*
* It checks if the properties on the right are all present in the object on
* the left. Note that the object might still have other properties (see
* objectContainsOnly below if you want the stricter form).
*
* The basic form does basic equality on each expected property:
*
* Assert.objectContains(fixture, {
* foo: "foo",
* bar: 1,
* baz: true,
* });
*
* But it also has a more powerful form with expectations. The available
* expectations are:
* - any(): this only checks for the existence of the property, not its value
* - number(), string(), boolean(), bigint(), function(), symbol(), object():
* this checks if the value is of this type
* - objectContains(expected): this applies Assert.objectContains()
* recursively on this property.
* - stringContains(needle): this checks if the expected value is included in
* the property value.
* - stringMatches(regexp): this checks if the property value matches this
* regexp. The regexp can be passed as a string, to be dynamically built.
*
* example:
*
* Assert.objectContains(fixture, {
* name: Expect.stringMatches(`Load \\d+:.*${url}`),
* data: Expect.objectContains({
* status: "STATUS_STOP",
* URI: Expect.stringContains("https://"),
* requestMethod: "GET",
* contentType: Expect.string(),
* startTime: Expect.number(),
* cached: Expect.boolean(),
* }),
* });
*
* Each expectation will translate into one or more Assert call. Therefore if
* one expectation fails, this will be clearly visible in the test output.
*
* Expectations can also be normal functions, for example:
*
* Assert.objectContains(fixture, {
* number: value => Assert.greater(value, 5)
* });
*
* Note that you'll need to use Assert inside this function.
*/
objectContains(object, properties) {
// Basic tests: we don't want to run other assertions if these tests fail.
if (typeof object !== "object") {
this.ok(
false,
`The first parameter should be an object, but found: ${object}.`
);
return;
}
if (typeof properties !== "object") {
this.ok(
false,
`The second parameter should be an object, but found: ${properties}.`
);
return;
}
for (const key of Object.keys(properties)) {
const expected = properties[key];
if (!(key in object)) {
this.ok(false, `The object should contain the property ${key}`);
continue;
}
if (typeof expected === "function") {
// This is a function, so let's call it.
expected(
object[key],
`The object should contain the property "${key}" with an expected value and type.`
);
} else {
// Otherwise, we check for equality.
this.equal(
object[key],
properties[key],
`The object should contain the property "${key}" with an expected value.`
);
}
}
},
/**
* This is very similar to the previous `objectContains`, but this also looks
* at the number of the objects' properties. Thus this will fail if the
* objects don't have the same properties exactly.
*/
objectContainsOnly(object, properties) {
// Basic tests: we don't want to run other assertions if these tests fail.
if (typeof object !== "object") {
this.ok(
false,
`The first parameter should be an object but found: ${object}.`
);
return;
}
if (typeof properties !== "object") {
this.ok(
false,
`The second parameter should be an object but found: ${properties}.`
);
return;
}
this.equal(
Object.keys(object).length,
Object.keys(properties).length,
"The 2 objects should have the same number of properties."
);
this.objectContains(object, properties);
},
});
const Expect = {
any: () => actual => {} /* We don't check anything more than the presence of this property. */,
};
/* These functions are part of the Assert object, and we want to reuse them. */
[
"stringContains",
"stringMatches",
"objectContains",
"objectContainsOnly",
].forEach(
assertChecker =>
(Expect[assertChecker] = expected => (actual, ...moreArgs) =>
Assert[assertChecker](actual, expected, ...moreArgs))
);
/* These functions will only check for the type. */
[
"number",
"string",
"boolean",
"bigint",
"symbol",
"object",
"function",
].forEach(type => (Expect[type] = makeTypeChecker(type)));
function makeTypeChecker(type) {
return (...unexpectedArgs) => {
if (unexpectedArgs.length) {
throw new Error(
"Type checkers expectations aren't expecting any argument."
);
}
return (actual, message) => {
const isCorrect = typeof actual === type;
Assert.report(!isCorrect, actual, type, message, "has type");
};
};
}
/* ------ End of assertion helper ------ */

Просмотреть файл

@ -0,0 +1,162 @@
add_task(function setup() {
// With the default reporter, an assertion doesn't throw if it fails, it
// merely report the result to the reporter and then go on. But in this test
// we want that a failure really throws, so that we can actually assert that
// it throws in case of failures!
// That's why we disable the default repoter here.
// I noticed that this line needs to be in an add_task (or possibly run_test)
// function. If put outside this will crash the test.
Assert.setReporter(null);
});
add_task(function test_objectContains() {
const fixture = {
foo: "foo",
bar: "bar",
};
Assert.objectContains(fixture, { foo: "foo" }, "Matches one property value");
Assert.objectContains(
fixture,
{ foo: "foo", bar: "bar" },
"Matches both properties"
);
Assert.objectContainsOnly(
fixture,
{ foo: "foo", bar: "bar" },
"Matches both properties"
);
Assert.throws(
() => Assert.objectContainsOnly(fixture, { foo: "foo" }),
/AssertionError/,
"Fails if some properties are missing"
);
Assert.throws(
() => Assert.objectContains(fixture, { foo: "bar" }),
/AssertionError/,
"Fails if the value for a present property is wrong"
);
Assert.throws(
() => Assert.objectContains(fixture, { hello: "world" }),
/AssertionError/,
"Fails if an expected property is missing"
);
Assert.throws(
() => Assert.objectContains(fixture, { foo: "foo", hello: "world" }),
/AssertionError/,
"Fails if some properties are present but others are missing"
);
});
add_task(function test_objectContains_expectations() {
const fixture = {
foo: "foo",
bar: "bar",
num: 42,
nested: {
nestedFoo: "nestedFoo",
nestedBar: "nestedBar",
},
};
Assert.objectContains(
fixture,
{
foo: Expect.stringMatches(/^fo/),
bar: Expect.stringContains("ar"),
num: Expect.number(),
nested: Expect.objectContainsOnly({
nestedFoo: Expect.stringMatches(/[Ff]oo/),
nestedBar: Expect.stringMatches(/[Bb]ar/),
}),
},
"Supports expectations"
);
Assert.objectContainsOnly(
fixture,
{
foo: Expect.stringMatches(/^fo/),
bar: Expect.stringContains("ar"),
num: Expect.number(),
nested: Expect.objectContains({
nestedFoo: Expect.stringMatches(/[Ff]oo/),
}),
},
"Supports expectations"
);
Assert.objectContains(fixture, {
num: val => Assert.greater(val, 40),
});
// Failed expectations
Assert.throws(
() =>
Assert.objectContains(fixture, {
foo: Expect.stringMatches(/bar/),
}),
/AssertionError/,
"Expect.stringMatches shouldn't match when the value is unexpected"
);
Assert.throws(
() =>
Assert.objectContains(fixture, {
foo: Expect.stringContains("bar"),
}),
/AssertionError/,
"Expect.stringContains shouldn't match when the value is unexpected"
);
Assert.throws(
() =>
Assert.objectContains(fixture, {
foo: Expect.number(),
}),
/AssertionError/,
"Expect.number shouldn't match when the value isn't a number"
);
Assert.throws(
() =>
Assert.objectContains(fixture, {
nested: Expect.objectContains({
nestedFoo: "bar",
}),
}),
/AssertionError/,
"Expect.objectContains should throw when the value is unexpected"
);
Assert.throws(
() =>
Assert.objectContains(fixture, {
num: val => Assert.less(val, 40),
}),
/AssertionError/,
"Expect.objectContains should throw when a function assertion fails"
);
});
add_task(function test_type_expectations() {
const fixture = {
any: "foo",
string: "foo",
number: 42,
boolean: true,
bigint: 42n,
symbol: Symbol("foo"),
object: { foo: "foo" },
function1() {},
function2: () => {},
};
Assert.objectContains(fixture, {
any: Expect.any(),
string: Expect.string(),
number: Expect.number(),
boolean: Expect.boolean(),
bigint: Expect.bigint(),
symbol: Expect.symbol(),
object: Expect.object(),
function1: Expect.function(),
function2: Expect.function(),
});
});

Просмотреть файл

@ -51,3 +51,5 @@ skip-if = tsan # Times out on TSan, bug 1612707
# See the comment on test_feature_stackwalking.js
[test_merged_stacks.js]
skip-if = (os == "mac" && release_or_beta) || (os == "linux" && release_or_beta && !debug) || asan || tsan
[test_assertion_helper.js]