Axional Test
.1 Introduction
Unit test programs are written in Javascript. Appart from standard Javascript libraries it can be used:
- The Axional JS Script libraries.
-
Axional Test
module library
2 Getting started
The Axional Test
module library is implemented by the test
clause.
It is both a function and a package.
The function enables test unit definitions and the package implements extra test functionallities as it will be further seen.
Then, the test(name, fn)
method allows run a test.
The first argument is the test name; the second argument is a function that contains the expectations to test.
For example, let's say there is a function fnMultiply() which receives two factors and returns the product. The whole test could be:
test("Eval 3x2=6", () => { const product = Ax.db.call("fnMultiply", 3, 2) Ax.jsunit.assertEquals(6, product); });
Each instance of Axional Test
Unit Test might content several tests.
It allows evaluating critical characteristics within the same test implementation.
For example:
test("Eval 3x2=6", () => { const product = Ax.db.call("fnMultiply", 3, 2) Ax.jsunit.assertEquals(6, product); }); test("Eval 2.5x2=5", () => { const product = Ax.db.call("fnMultiply", 2.5, 2) Ax.jsunit.assertEquals(5, product); }); test("Eval -3x2=-6", () => { const product = Ax.db.call("fnMultiply", -3, 2) Ax.jsunit.assertEquals(-6, product); });
The previous case can be optimized by executing dynamic tests:
[ [3, 2, 6], [2.5, 2, 5] [-3, 2, 6] ].forEach(testcase => { test(´Eval ${testcase[0]}x${testcase[1]}=${testcase[2]}´, () => { const product = Ax.db.call("fnMultiply", ${testcase[0]}, ${testcase[1]}) Ax.jsunit.assertEquals(${testcase[2]}, product); }); });
Axional Test
Unit test instance will produce one response for each execution of test(name, fn)
method.
For that reason, the second and third examples are equivalent.
3 Test response
The execution of an Axional Test
Unit test instance produces one response for each execution of test(name, fn) method.
So, one test implementation might produce more than one response.
A test response can produce three different responses:
- Succeed, when the execution is not interrupted by any assertion nor exception.
- Failed, when the execution is interrupted by an unsuccessful assertion.
- Error, when the execution is interrupted by an exception.
3.1 Successful response
A test execution is successful when is not interrupted by any assertion nor exception. The following code implements a sample of a test execution which produces a successful response:
test('Succeed test', () => { Ax.jsunit.assertEquals(10, 10); });
Result:
Name | Result | Message |
---|---|---|
Succeed test | Succeed |
3.2 Failure response
A test execution is failed when is interrupted by an unsuccessful assertion. The following code implements a sample of a test execution which produces a failure response:
test('Failed test', () => { Ax.jsunit.assertEquals(10, 15); });
Result:
Name | Result | Message |
---|---|---|
Failed test | Failed | Expected 10 (java.lang.Integer) while found 15 (java.lang.Integer) |
3.3 Error response
A test execution is marked with error when is interrupted by an exception. The following code implements a sample of a test execution which produces an error response:
test('Error test', () => { Ax.jsunit.assertEquals(10, amount); });
Result:
Name | Result | Message |
---|---|---|
Error test | Error | Error: Undefined function or variable 'amount' |
3.4 Test with multiple assertions
Each test unit might contain multiple assertions. The test is executed while the assertions found are successful. So, if a single assertion fails the unit test fails. The following code implements a sample of a test executing multiple assertion which produces a failure response:
test('Multiple assertions', () => { Ax.jsunit.assertEquals(10, 10); Ax.jsunit.assertEquals(10, 15); });
Result:
Name | Result | Message |
---|---|---|
Failed test | Failed | Expected 10 (java.lang.Integer) while found 15 (java.lang.Integer) |
3.5 Multiple test executions
As it has been mentioned before, an instance of Axional Test
Unit test can contain multiple test units.
Every execution of a test unit produces a response, as it can be seen in the following example:
test('Succeed test', () => { Ax.jsunit.assertEquals(10, 10); }); test('Failed test', () => { Ax.jsunit.assertEquals(10, 15); }); test('Error test', () => { Ax.jsunit.assertEquals(10, amount); });
Result:
Name | Result | Message |
---|---|---|
Succeed test | Succeed | |
Failed test | Failed | Expected 10 (java.lang.Integer) while found 15 (java.lang.Integer) |
Error test | Error | Error: Undefined function or variable 'amount' |
When having multiple unit test the global state of the test instance is the most severe. So, status severity sorted from the most severe to the lowest is: Error, Failure, Succeed. In consequence, the global state of the previous sample would be Error, even though it contains successful and failed tests.
4 Transaction handling
Tests are executed inside a transaction that is rolled back when all tests units have finished. This fact helps keeping test data consistent and prevents that one test execution influences other tests or future executions of the current test.
Let's see an example of it:
test('Insert test', () => { Ax.db.insert("product", { name: "Water 500ml", price: 0.35 }); const count = Ax.db.executeQuery("SELECT COUNT(*) count FROM product WHERE name = 'Water 500ml'").toOne().count; Ax.jsunit.assertEquals(1, count); });
As it has been mentioned before, even the test success, after its execution the database will not have any product named 'Water 500ml'. Let's see the transaction flux of sample:
Notice that it only exists one transaction for the whole test, so inner test units executions will be included in the same transaction.
Let's see an example of it:
test('Insert test', () => { Ax.db.insert("product", { name: "Water 500ml", price: 0.35 }); const count = Ax.db.executeQuery("SELECT COUNT(*) count FROM product WHERE name = 'Water 500ml'").toOne().count; Ax.jsunit.assertEquals(1, count); }); test('Count test', () => { const count = Ax.db.executeQuery("SELECT COUNT(*) count FROM product WHERE name = 'Water 500ml'").toOne().count; Ax.jsunit.assertEquals(1, count); });
In this case, both tests units will success, and after test execution the database will not have any product named 'Water 500ml' either. Let's see the transaction flux of sample:
IMPORTANT
You should never use the commit function in a test when operating agaist the Master Test Data: an error will occur due to some functions.
5 Repeating Setup For Many Tests
When it is needed to do some work repeatedly for many tests, the test.beforeEach(fn)
and test.afterEach(fn)
methods can be used.
For example, in case of implementing a test for evaluating how triggers works in different situations. Let's say that several tests interact with a database of sales. There is an entity sale_head which contains the registry of each sale and an entity sale_line cointaining the items sold on each sale. The total amount of the sale is stored in the sale_head.amount and it is automatically updated via database triggers on each transaction of sale_line:
test.beforeEach(() => { Ax.db.insert("sale_head", { num: "O901", date: new Date() }); }); test.afterEach(() => { Ax.db.delete("sale_head", { num: "O901" }); }); test('Single insert', () => { Ax.db.insert("sale_line", { num: "O901", item: "Coke 200ml", qty: 1, price: 0.48 }); const amount = Ax.db.executeQuery("SELECT amount FROM sale_head WHERE num = 'O901'").toOne(); Ax.jsunit.assertEquals(0.48, amount); }); test('Multiple inserts', () => { Ax.db.insert("sale_line", { num: "O901", item: "Coke 200ml", qty: 1, price: 0.48 }); Ax.db.insert("sale_line", { num: "O901", item: "Mineral water 500ml", qty: 5, price: 0.35 }); const amount = Ax.db.executeQuery("SELECT amount FROM sale_head WHERE num = 'O901'").toOne(); Ax.jsunit.assertEquals(2.23, amount); });
The graph bellow shows the test execution flux of the previous test sample:
WARNING
test.beforeEach(fn)
and test.afterEach(fn)
do not cause any type of output, so no assertions should be made within these blocks.
6 One-Time Setup
In some cases, it is only need to do setup once, at the beginning or the end of the test execution.
Axional Test
provides test.beforeAll(fn)
and test.afterAll(fn)
to handle this situation.
WARNING
test.beforeAll(fn)
and test.afterAll(fn)
do not cause any type of output, so no assertions should be made within these blocks.
Recuperating the previous sample, but considering that the execution of the first test affects the second one, might lead to the following example:
test.beforeAll(() => { Ax.db.insert("sale_head", { num: "O901", date: new Date() }); }); test.afterAll(() => { Ax.db.delete("sale_head", { num: "O901" }); }); test('Eval insert', () => { Ax.db.insert("sale_line", { num: "O901", item: "Coke 200ml", qty: 1, price: 0.48 }); const amount = Ax.db.executeQuery("SELECT amount FROM sale_head WHERE num = 'O901'").toOne(); Ax.jsunit.assertEquals(0.48, amount); }); test('Eval update', () => { Ax.db.update("sale_line", { qty: 2 }, { num: "O901", item: "Coke 200ml" } ); const amount = Ax.db.executeQuery("SELECT amount FROM sale_head WHERE num = 'O901'").toOne(); Ax.jsunit.assertEquals(0.96, amount); });
The graph bellow shows the test execution flux of the previous test sample:
Notice that the DELETE statement at the test.afterAll
clause has been added for giving content to the sample explanation.
But, as test execution do transaction rollback at the end of the test, it would not be necessary.
7 Assertions
Axional Test
module uses "assertions" to let you test values in different ways.
Each test unit is strongly recommended to have at least one assertion.
This section will introduce some commonly used assertions.
For the full list, see the Axional JS Script Ax.junit
API doc.
7.1 Common assertions
The simplest way to test a value is with exact equality:
-
Ax.jsunit.assertEquals
, matches anything that an if statement treats two variables as equal
test('two plus two is four', () => { Ax.jsunit.assertEquals(4, 2 + 2); });
Conversely, test that two values are different:
-
Ax.jsunit.assertNotEquals
, matches anything that an if statement treats two variables as not equal
test('two plus two is not five', () => { Ax.jsunit.assertNotEquals(5, 2 + 2); });
7.2 Truthiness
In tests sometimes is needed to distinguish between undefined, null, false, etc., and sometimes not.
Axional JS Script Library
contains helpers that let be explicit about that.
-
Ax.jsunit.assertTrue
, matches anything that an if statement treats as true -
Ax.jsunit.assertFalse
, matches anything that an if statement treats as false -
Ax.jsunit.assertEmpty
, matches the values null, undefined, '', [] or {} -
Ax.jsunit.assertNotEmpty
, matches anything that an Ax.jsunit.assertEmpty statement treats as false -
Ax.jsunit.assertNull
, matches the values null or undefined -
Ax.jsunit.assertNotNull
, matches anything that an Ax.jsunit.assertNull statement treats as false
Some examples of use are:
test("null", () => { const n = null; Ax.jsunit.assertFalse(n); Ax.jsunit.assertEmpty(n); });
test("zero", () => { const z = 0; Ax.jsunit.assertFalse(z); Ax.jsunit.assertNotEmpty(z); });
7.3 Arrays and iterables
It can be checked if an array or iterable contains a particular item using indexOf and assertTrue
assertion:
test("Array contains", () => { const a = ['Barcelona', 'London', 'Paris']; Ax.jsunit.assertTrue(a.indexOf('Barcelona') >= 0); });
7.4 Dates
In programming languages dates handling is a common source of troubles and headaches.
Generally, because depending on the programming language date types might be slightly different (consider time, calendar, etc.).
Furthermore, in Axional Studio
applications intervenes several programming languages in the same code (SQL, XSQL-Script, Javascript), thing that increases the problem.
For example, in case of implementing a test for evaluating a certain date value from a database of sales. There is an entity sale_head which contains a registry with the sales number 'O901' which its date of creation is May 21, 2018 23:15:05 (UTC). The date of creation is stored in the attribute date_created, with precision from year to second:
test("Date 'yyyy-mm-dd HH:mm:ss' verfication", () => { const date_created = Ax.db.executeQuery("SELECT date_created FROM sale_head WHERE num = 'O901'").toOne().date_created; Ax.jsunit.assertEquals(new Date("2018-05-21 23:15:05"), date_created); });
Despite this, the most common dates verification in business applications does not consider time, but days.
In order to easy those verifications it can be used the assertDateEquals
assertion:
test("Date 'yyyy-mm-dd' verfication", () => { const date_created = Ax.db.executeQuery("SELECT date_created FROM sale_head WHERE num = 'O901'").toOne().date_created; Ax.jsunit.assertEquals(new Date("2018-05-21"), date_created); });
Notice that, the variable date_created contains the value of hours, minutes and seconds, and the value created by new Date("2018-05-21") not.
in consequence, in that case the assertEquals
execution will fail.
"2018-05-21 23:15:05" -> assertEquals
-> "2018-05-21" = Test Failed
test("Date 'yyyy-mm-dd' verfication", () => { const date_created = Ax.db.executeQuery("SELECT date_created FROM sale_head WHERE num = 'O901'").toOne().date_created; Ax.jsunit.assertDateEquals(new Date("2018-05-21"), date_created); });
"2018-05-21 23:15:05" -> assertDateEquals
-> "2018-05-21" = Test Succeed
7.5 Snapshoting
Snapshot tests are a very useful tool when evaluating that complex data does not change unexpectedly. A snapshot is a "picture" of a piece of information at a certain moment. So, when execute assertions against a snapshot, it evaluates that the current snapshot is the same as the model snapshot. The model snapshot is the picture taken when test executes for first time. Consequently, the first time a snapshot test is executed will never fail, so it will only store the taken snapshot as model.
WARNING
When a snapshot model is created for the first time or after performing a reset, it is essential to verify the validity of its content. An incorrect snapshot model will invalidate any test result.
A typical snapshot test case for an accounting app generates an XML structure with the accounting data of a period, takes a snapshot, then compares it to a reference snapshot model stored alongside the test. The test will fail if the two snapshots do not match: either the change is unexpected, or the reference snapshot needs to be updated to the new version of the XML structure.
In order to execute simple assertions against a snapshot, use assertEquals
.
test("Model 303 for 2018", (test_unit) => { const model_303 = Ax.db.call("accounting_calculate_303", { period_from: new Date("2018-01-01"), period_to: new Date("2018-12-31") }) Ax.jsunit.assertEquals(test_unit.snapshot, model_303); });
It is important to note that the test function receives the param test_unit, which is used to reference the snapshot for the current test unit. In consequence, each test unit can only contain a single snapshot. In case of require more than one the unit must be split in several tests.
Bad use
test("Model 303 for 2017 and 2018", (test_unit) => { const model_303_2017 = Ax.db.call("accounting_calculate_303", { period_from: new Date("2017-01-01"), period_to: new Date("2017-12-31") }) Ax.jsunit.assertEquals(test_unit.snapshot, model_303_2017); const model_303_2018 = Ax.db.call("accounting_calculate_303", { period_from: new Date("2018-01-01"), period_to: new Date("2018-12-31") }) Ax.jsunit.assertEquals(test_unit.snapshot, model_303_2018); });
Good use
test("Model 303 for 2017", (test_unit) => { const model_303 = Ax.db.call("accounting_calculate_303", { period_from: new Date("2017-01-01"), period_to: new Date("2017-12-31") }) Ax.jsunit.assertEquals(test_unit.snapshot, model_303); }); test("Model 303 for 2018", (test_unit) => { const model_303 = Ax.db.call("accounting_calculate_303", { period_from: new Date("2018-01-01"), period_to: new Date("2018-12-31") }) Ax.jsunit.assertEquals(test_unit.snapshot, model_303); });
7.5.1 Snapshot read
When the test is run for the first time, the Model snapshot is registered. From the Execution results area in the Test Definition form it is possible to unload the Model Snapshot file by means the diskette icon (blue color when available).

The subsequent times, test result will be registered in the Snapshot Execution file, comparing this file with the model file (both are excel files). Remember that when a Test_Unit has been divided into several tests, also several responses to each snapshot will be obtained.
- When both snapshot files match, the response will be Succeed.
- If the response is Failed, the application displays a short message about the failure. To make an intensive comparison, this file can also be downloaded using the blue diskette.
7.5.2 Updating snapshots
When the Snapshot model is no longer valid, it will be necessary to update the Model file. To that end, access the Test Definition / Unit test and select desired Test:
- Use the Reset button to delete snapshot files: this will erase both files, model and execution files. In fact, it will erase the entire row, including all counters, user, date, etc...
- Execute the test: this test result will be recorded and it will become the new model until next reset.
7.6 Business data
Axional Test
module is usually used for evaluating business data.
Generally, those evaluations include complex information and a huge amount of attributes which becomes difficult to analize.
Let's see an example:
test("Test sales triggers", (test_unit) => { Ax.db.insert("sale_head", { num: "O901", date: new Date() }); Ax.db.insert("sale_line", { num: "O901", item: "Coke 200ml", qty: 1, price: 0.48 }); Ax.db.insert("sale_line", { num: "O901", item: "Mineral water 500ml", qty: 5, price: 0.35 }); const head_amount = Ax.db.executeQuery("SELECT amount FROM sale_head WHERE num = 'O901'").toOne(); Ax.junit.assertEquals(2.23, head_amount); const rsSaleLines = Ax.db.executeQuery("SELECT amount, item FROM sale_line WHERE num = 'O901'"); // Evaluate first line is correct Ax.jsunit.assertTrue(rsSaleLines.next()); Ax.junit.assertEquals("Coke 200ml", rsSaleLines.toRow().item); Ax.junit.assertEquals(0.48, rsSaleLines.toRow().amount); // Evaluate second line is correct Ax.jsunit.assertTrue(rsSaleLines.next()); Ax.junit.assertEquals("Mineral water 500ml", rsSaleLines.toRow().item); Ax.junit.assertEquals(1.75, rsSaleLines.toRow().amount); // Evaluate the are no more lines Ax.jsunit.assertFalse(rsSaleLines.next()); rsSaleLines.close(); });
The previous code is completely correct, but it might be quite complicated and it could even become more tangled as more data is added to verify.
In order to test that a particular verifications, use assertResultSet
.
test("Test sales triggers with resultSet assertion", (test_unit) => { Ax.db.insert("sale_head", { num: "O901", date: new Date() }); Ax.db.insert("sale_line", { num: "O901", item: "Coke 200ml", qty: 1, price: 0.48 }); Ax.db.insert("sale_line", { num: "O901", item: "Mineral water 500ml", qty: 5, price: 0.35 }); Ax.jsunit.assertResultSet(test_unit.snapshot, [ { tableName: "sale_head", columns : "num,date,amount", where : "num = '0901'" }, { tableName: "sale_line", columns : "item,qty,price,amount", where : "num = '0901'" } ]); });
assertResultSet
must be always executed against an snapshot.
7.7 Exceptions
In order to test that a particular function throws an error when it's called, use assertThrows
.
test("Call throws exception", () => { Ax.jsunit.assertThrows("Cannot delete invoice because is already accounted", () => { Ax.db.delete("invoice", { num: "I2019-00001" }); }); });