unitTest ReferenceThe unitTest object implements an internal self-test function. It is similar to Jest, but it relies on ordinary JavaScipt operators instead of learning a new vocabulary of testing operators.
The unitTest object creates and controls tests. Tests are created using the unitTest.create() function. This takes a test title and a test function as arguments. The test function is called when the test is run and is passed a single argument, test, that is a sub-test function. When the test function is called, it calls test(subTestTitle) to create a sub-test object. the sub-test object takes chained methods to execute the test and determine the results. Here's an example of a test for Ptable interpolation:
unitTest.create('Ptable', (test) => {
// test simple Ptsble interpolation
const table = new Ptable({
title: "Simple Table",
inputs: [{key:'parm1'}],
a: [
{p:0, v:0},
{p:10, v:10},
]
});
test(`Simple table: interpolate: value`)
.isValue(table.interpolate({parm1: 4.6}), 4.6);
test(`Simple table: interpolate: passed rndMult`)
.isValue(table.interpolate({parm1: 4.6}, {rndMult:1}), 5);
});
The title passed to unitTest.create()documents the test series, and the title passed to each test() invocation documents each sub-test. The isValue method asserts that the computed value in the first argument is the same as in the second argument.
Unit tests are typically created at load time and test the module they are associated with.
Some tests may test functions that work asynchronously. In that case the test sub-test function can take a Promise as an argument. The Promise then runs the sub-test function when it has results. This feature can be user to, for example, test fetching from a server.
The sub-test function returns an instance of the Test class. The Test class has a set of assertion methods that determine the success or failure of each sub-test. The methods can be extended or overridden by passing a sub-class of Test as the third argument to unitTest.create() function. The base set of methods are:
pass()fail(message)message.assert(isOk, message)isOk is true. If not, the test fails with message.isValue(value, requiredValue)value is equal to (===) requiredValue. If not, the test fails.isNotValue(value, requiredValue)value is not equal to (!==) requiredValue. If not, the test fails.isApprox(value, requiredValue, error)value is not an Error, is valid, and is equal to requiredValue within ± error. If not, the test fails. error may be a string ending in %.isSvValue(idOrSv, requiredValue)idOrSv is equal to (===) requiredValue. If not, the test fails.isNotSvValue(idOrSv, requiredValue)idOrSv is not equal to (!==) requiredValue. If not, the test fails.isError(value)value is an Error. If not, the test fails.isSvApprox(idOrSv, requiredValue, error)idOrSv is not an error, is valid, and is equal to requiredValue within ± error. error may be a string ending in %. If not, the test fails.isSvValid(idOrSv)idOrSv is valid. If not, the test fails.throwsError(fn, checkErrFn)fn throw an error and that the error is correct according to checkErrFn.Methods other than pass() and fail() must eventually call pass() or fail() to declare the test end state.
Each call to unitTest.create() registers a group of tests under its title. The tests are not run until later. Calling unitTest.runOne(title) will run a test that matches the title and returns a Promise that resolves with the test results. The app's state is reset to default values before the test is run to put the app in a consistent state. Popup dialogs are suppressed (dialog.suppress = true) while the test runs and are re-enabled when it ends. The resolved value is an object that contains a title, result, and results properties that contain the test title. either pass or fail, and a Map of sub-test results, respectively. In the Map, the key is normally the sub-test title and the value is an object containing a message. If the message is empty, the sub-test passed. The Map may also contain entries keyed by Symbols for problems that belong to no sub-test (see unitTest.runOne()).
While a test runs, an assert() failure in the code under test fails the test. unitTest sets assert.onFailure for the duration of the run, so instead of logging and displaying an alert that nobody sees during a test run, the assertion message becomes the result of the sub-test that is running. The assertion message replaces the sub-test's own result whether the assertion happens before or after the sub-test records it, and the first assertion in a sub-test is the one reported. An assertion during the state reset, or before the first sub-test is created, is recorded against the test group rather than a sub-test. The hook is in place from the state reset until any promised results are in, so asynchronous sub-tests are covered too; an assertion in an asynchronous sub-test is attributed to the most recently created sub-test.
Calling the unitTest.runAll() function runs all tests and returns a Promise that resolves with the results. The app's state is saved before and restored after the tests are run. In addition, storing the state locally or in the cloud is disabled while the test is running, and popup dialogs are suppressed.
The tests are run by chaining the Promise returned by calling unitTest.runOne() for each test. The test Promise resolves when the chain of Promises resolves.
The unitTest.renderResults() function displays the results in a standard format. The results can be summarized or detailed. The summarized results always list the titles of the passed tests under "Passed tests". If any test fails, a "Failed tests" list is shown as well, giving the test, the sub-test, and its message. There is no separate "all passed" indicator. In the detailed presentation, the results of all tests and sub-tests are displayed.
unitTest methodsunitTest.create()unitTest.create(title, fn)
unitTest.create(title, fn, type)
Creates a new test with title. The title must be unique among created tests, and type must be Test or a subclass of it; otherwise an assert() failure is raised. The fn argument is a function to call to run the test. The test function takes a single test argument which is also a function. The test function calls the test to initiate each sub-test, passing a string that describes the sub-test as an argument. The test function returns an instance of the Test class. The sub-test must then call one of the Test instance methods to check the test conditions. All methods eventually invoke the pass() or fail(message) to declare the test result.
Once created, the units tests are executed by calling unitTest.runOne() to run a specified test or unitTest.runAll() to run all tests.
titlefntesttest(title), where title is the title of the sub-test. The function creates a sub-test with title and returns an instance or subclass of the Test class.type (optional)Test to return from the sub-test function. If omitted, defaults to Test. The subclass can override Test methods or add new ones. All methods must eventually result in a call to the pass() or fail() method in the superclass.unitTest.getTests()unitTest.getTests()
Returns an array of all the created test titles.
An array of strings.
unitTest.resetScenario()unitTest.resetScenario()
Resets the shared scenario state that test groups otherwise inherit from whichever group ran before them: departure and destination airports, field elevations, OAT, altimeter settings, and winds, plus the enroute altimeter and wind. Page test groups call this first so their results do not depend on group order. Each state variable is set only if the app defines it.
unitTest.renderResults()unitTest.renderResults(results)
unitTest.renderResults(results, isDetailed)
Returns a Template displaying the results. By default, a "Failed tests" list (failed test titles, their sub-test titles, and their error messages; present only if any test failed) is followed by a "Passed tests" list of the titles of the passing tests.
If detatiled is present and truthy, the results of all test and sub-test are presented.
resultsunitTest.runAll().isDetailed (optional)unitTest.renderResults() will display all results, including passed tests.unitTest.runAll()unitTest.runAll()
unitTest.runAll(showProgressFn)
Calling the unitTest.runAll() function will run all registered tests serially. It returns a Promise that resolves with the test results. The results are an array of the objects returned by unitTest.runOne() function. The function does the following:
ctl.doSync() to synchronize the Model.storage.getState() to get the current state saved in the local device storage.storage.disable() to disable saving state in storage during the test run.unitTest.getTests() function to get the list of registered tests.showProgressFn argument was passed to unitTest.runAll(), it calls the showProgressFn function, passing the title of the test about to be run. The showProgressFn can use the title to update a state variable that displays the progress.unitTest.runOne() function to run the test.unitTest.runAll() function always does the following:dialog.suppress = true so the restore does not raise popup dialogs.ctl.resetAppValues() to reset the state variables to defaults.ctl.doSync() to synchronize the Model.ctl.setSavedState() to to restore the state saves at the start of the run.ctl.doSync() to synchronize the Model.storage.enable() to enable saving state to storage.ctl.save() to save the restored state.dialog.suppress to false.unitTest.runOne() during the test.showProgressFn (optional)A Promise that resolves with the test results. The results value is an array of objects returned by unitTest.runOne() function.
unitTest.runOne()unitTest.runOne(title)
Calling the unitTest.runOne() function will run a test that matches the title passed as an argument. The function returns a Promise that resolves with the test results. While executing the Promise, unitTest.runOne() does the following:
dialog.suppress = true so popup dialogs are auto-answered, and installs the assert.onFailure hook. The hook and dialog.suppress are reset when the returned Promise settles.ctl.resetAppValues() to reset all the state variables to their default values.ctl.doSync() to synchronize the Model.try block to catch exceptions.unitTest.runOne() resolves with a value that is an object containing the following properties:title
The title of the test.
result
Either 'pass' or 'fail'.
results
A Map containing the results. The Map keys are normally the sub-test titles, and the value is an object that contains a message property. If the value of the message property has content, then the sub-test has failed, and the value is the error message. Otherwise, the sub-test has passed. If the title for the sub-test was not unique within the test, then a number is added to the end of the title to make it unique.
The Map can also contain entries whose keys are Symbols (_exception, _assertion, _asyncError, _asyncMismatch) that record problems not tied to a sub-test. Their messages are, respectively: "Uncaught exception during test run: ..." (the test function threw), "Assertion failure: ..." (an assert() failed before any sub-test was created), "Promise rejection during test run: ..." (a promised result rejected), and "N missing promised results during test run" (fewer promised sub-test results arrived than expected). Any such entry makes the test fail. unitTest.renderResults() shows these messages without a sub-test title.
titleTest classnew Test(doneFn)
The Test constructor builds an instance whose methods are used to make assertions about the results of a sub-test. The instance is used as a return value from the te sub-test function passed to each test. A subclass can be used as well.
The doneFn argument is a function prepared by unitTest.runOne(). The test assertion methods call doneFn with a single message argument to record the results of the test. If the message is empty, the test has passed. In the Test class only the pass() and fail() methods call doneFn. All the other methods call pass() or fail() to record results.
doneFnmessage argument and records the message as the results of the sub-test. The test is considered to have passed if the message is empty.pass()pass()
This method records that the sub-test has passed.
fail()fail(message)
This method records that the sub-test has failed with the error message message.
messageassert()assert(isOk, message)
This method records that the sub-test has failed with the error message message is isOk is falsey. Otherwise, it records that the sub-test has passed.
isOkisOk is truthy.messageisOk failed.isValue()isValue(value, requiredValue)
This method records that the sub-test has passed if value is equal to (using ===) requiredValue. Otherwise, it records that the sub-test has failed, with a message indicating that both values did not match.
valuerequiredValuevalue === requiredValue.isNotValue()isNotValue(value, requiredValue)
This method records that the sub-test has passed if value is not equal to (using !==) requiredValue. Otherwise, it records that the sub-test has failed, with a message indicating that both values matched.
valuerequiredValuevalue !== requiredValue.isApprox()isApprox(value, requiredValue, error)
This method records that the sub-test has passed if value is not an Error, is valid (SV.isValid(value)), and is within ± error of requiredValue. Otherwise, it records that the sub-test has failed, with a message indicating that value was not within the required interval.
valuerequiredValueerrorerror is a string with a number ending with "%", then the error value will be converted to that percentage of requiredValue. The test will pass if:Math.abs(value - requiredValue) <= error + [EPSILON](#../utilities/numericfunctions#epsilon).isError()isError(value)
This method records that the sub-test has passed if value is an Error object. Otherwise, it records that the sub-test has failed, with a message indicating that value is not an Error.
valueisSvValue()isSvValue(idOrSv, requiredValue)
This method records that the sub-test has passed if the value in the state variable or the state variable identified by SV.getSv(idOrSv).value is equal to (using ===) requiredValue. Otherwise, it records that the sub-test has failed, with a message indicating that both values did not match.
idOrSvrequiredValueSV.getSv(idOrSv) === requiredValue.isNotSvValue()isNotSvValue(idOrSv, requiredValue)
This method records that the sub-test has passed if SV.getSv(idOrSv).value is not equal to (using !==) requiredValue. Otherwise, it records that the sub-test has failed, with a message indicating that both values matched.
idOrSvrequiredValueSV.getSv(idOrSv) !== requiredValue.isSvApprox()isSvApprox(idOrSv, requiredValue, error)
This method records that the sub-test has passed if SV.getSv(idOrSv).value is not an error value (SV.isSvError()), the state variable is valid (isValid()), and the value is within ± error of requiredValue. Otherwise, it records that the sub-test has failed, with a message indicating that value was not within the required interval.
idOrSvrequiredValueerrorerror is a string with a number ending with "%", then the error value will be converted to that percentage of requiredValue. The test will pass if:Math.abs(SV.getSv(idOrSv).value - requiredValue) <= error + [EPSILON](#../utilities/numericfunctions#epsilon).isSvValid()isSvValid(idOrSv)
This method records that the sub-test has passed if SV.getSv(idOrSv).isValid() is true. Otherwise, it records that the sub-test has failed, with a message indicating that the state variable does not contain a valid value.
idOrSvthrowsError()throwsError(fn)
throwsError(fn, checkErrFn)
This method records that the sub-test has passed if calling fn() results in a thrown error and either checkErrorFn is omitted or calling checkErrorFn(error) passing the received error returns an empty message string. Otherwise, it records that the sub-test has failed, with a message indicating that calling the function did not result in a thrown error or the non-empty message returned by calling checkErrorFn(error).
fncheckErrFn (optional)