@@ -11,7 +11,7 @@ Provides basic assertion capabilities and a structured test runner for easy test
1111- ** TestRunner** : Structured, hierarchical output with configurable verbosity levels (` OFF ` , ` HEADER ` , ` SECTION ` , ` SUBSECTION ` ).
1212- ** Compatible** : Runs on both Office Scripts and Node/TypeScript (for local or CI testing).
1313- ** Simple** : No dependencies, no decorators, no runtime imports.
14- - ** Extendable ** : Add your own assertions or test conventions easily.
14+ - ** Extensible ** : Add your own assertions or test conventions easily.
1515
1616---
1717
@@ -20,19 +20,19 @@ Provides basic assertion capabilities and a structured test runner for easy test
2020### 1. Clone or copy this repo
2121
2222Place ` unit-test-framework.ts ` in your project.
23- (Optional: Use ` main.ts ` as a starting point for your test suite.)
23+ (Optional: Use ` test/ main.ts` as a starting point for your test suite.)
2424
2525### 2. Write Tests
2626
2727Define a ` TestRunner ` and create a test class with static methods, e.g.:
2828
2929``` typescript
30- runner = new TestRunner (TestRunner .VERBOSITY .SECTION ) // Define the test case runneer and verbosity level
31- runner .title (" Start Testing" , 1 ) // Sending the title to console indicating the test started
32- run .exec (" Test Case for math" , () => TestCase .math (), 1 ) // Executing math method from TestCase
33- runner .title (" End Testing" , 1 ) // Sending the title to console indicating the test ended
30+ const runner = new TestRunner (TestRunner .VERBOSITY .SECTION ) // Define the test case runner and verbosity level
31+ runner .title (" Start Testing" , 1 ) // Output title indicating the test started
32+ runner .exec (" Test Case for math" , () => TestCase .math (), 2 ) // Execute math method from TestCase with section indentation level
33+ runner .title (" End Testing" , 1 ) // Output title indicating the test ended
3434
35- // Class where to organize all test cases
35+ // Class to organize all test cases
3636class TestCase {
3737 public static math(): void {
3838 Assert .equals (2 + 2 , 4 , " Addition works" )
@@ -41,7 +41,7 @@ class TestCase {
4141 }
4242}
4343```
44- ** Note:** ` TestCase ` class is not requied , just a way to organize all test cases to be executed via ` TestRunner ` class.
44+ ** Note:** The ` TestCase ` class is not required , just a way to organize all test cases to be executed via the ` TestRunner ` class.
4545
4646### 3. Run Tests
4747
@@ -64,9 +64,9 @@ Assert.equals(actual, expected, "optional message")
6464- For arrays, each element is checked for both type and value. For objects/arrays of objects, a deep check (using JSON.stringify) is performed.
6565- Example:
6666 ``` typescript
67- Assert .equals ([1 , 2 , 3 ], [1 , 2 , 3 ], " Arrays are equal" ). // Passes: Arrays are equals. Using optional message
68- Assert .equals ([1 , " 2" ], [1 , 2 ]) // Fails: type mismatch at index 1
69- Assert .equals ([{x:1 }], [{x:1 }]) // Passes: objects are deeply equal
67+ Assert .equals ([1 , 2 , 3 ], [1 , 2 , 3 ], " Arrays are equal" ) // Passes
68+ Assert .equals ([1 , " 2" ], [1 , 2 ]) // Fails: type mismatch at index 1
69+ Assert .equals ([{x:1 }], [{x:1 }]) // Passes: objects are deeply equal
7070 ```
7171
7272#### Inequality
@@ -164,6 +164,7 @@ Assert.doesNotThrow(
164164** Note:**
165165` Assert.throws ` requires ** the throwing code to be passed as a function reference** (using ` () => ... ` or ` function() { ... } ` ).
166166This allows the assertion method to execute your function and catch any exceptions inside its own logic.
167+
167168#### Fail Manually
168169
169170``` typescript
@@ -182,13 +183,13 @@ const runner = new TestRunner(TestRunner.VERBOSITY.SECTION) // or HEADER, OFF, S
182183
183184#### Verbosity Levels
184185
185- - ` OFF ` (0 ): No output.
186- - ` HEADER ` (1 ): Only top-level section headers.
187- - ` SECTION ` (2 ): Section and higher.
188- - ` SUBSECTION ` (3 ): All titles, including subsections.
186+ - ` OFF ` (` 0 ` ): No output.
187+ - ` HEADER ` (` 1 ` ): Only top-level section headers.
188+ - ` SECTION ` (` 2 ` ): Section and higher.
189+ - ` SUBSECTION ` (` 3 ` ): All titles, including subsections.
189190
190191** How verbosity and indent work:**
191- - Each call to ` runner.title("Title", indent) ` prints the message with ` indent ` number of ` * ` as prefix and suffix (e.g., ` ** title ** ` for indent=2).
192+ - Each call to ` runner.title("Title", indent) ` prints the message with ` indent ` number of ` * ` as prefix and suffix (e.g., ` ** title ** ` for ` indent=2 ` ).
192193- A title is only printed if its ` indent ` is ** less than or equal to** the current verbosity.
193194- This lets you control granularity of test output: higher verbosity shows more detail.
194195
@@ -220,49 +221,97 @@ runner.getVerbosityLabel() // returns "HEADER", etc
220221## Example: Full Test Suite
221222
222223``` typescript
224+ // main test file for the unit test framework
225+
223226function main(workbook : ExcelScript .Workbook ) {
224227 const runner = new TestRunner (TestRunner .VERBOSITY .SECTION )
225228 let success = false
226229 try {
227230 runner .title (" Running All Tests" , 1 )
228- runner .exec (" Math Test" , () => {
229- Assert .equals (2 + 3 , 5 )
230- Assert .notEquals (2 * 2 , 5 )
231- Assert .equals ([1 , 2 ], [1 , 2 ], " Array equality" )
232- Assert .throws (() => { throw new TypeError (" fail" ) }, TypeError , " fail" , " Throws test" )
233- }, 2 )
234- runner .exec (" Null/Undefined Test" , () => {
235- Assert .isNull (null )
236- Assert .isNotNull (0 )
237- Assert .isUndefined (undefined )
238- Assert .isNotUndefined (" " )
239- Assert .isDefined (123 )
240- }, 2 )
241- runner .exec (" Instance Test" , () => {
242- class Animal {}
243- class Dog extends Animal {}
244- const d = new Dog ()
245- Assert .isInstanceOf (d , Dog )
246- Assert .isInstanceOf (d , Animal )
247- Assert .throws (() => Assert .isInstanceOf ({}, Dog ), AssertionError , undefined , " Throws if not instance" )
248- Assert .isNotInstanceOf ({}, Dog )
249- }, 2 )
250- runner .exec (" Throws/DoesNotThrow Test" , () => {
251- const thrower = () => { throw new Error (" fail" ) }
252- const nonThrower = () => { return 42 }
253- Assert .throws (thrower , Error , " fail" , " Should throw error" )
254- Assert .doesNotThrow (nonThrower , " Should not throw" )
255- }, 2 )
256- runner .exec (" Type Test" , () => {
257- Assert .assertType (" abc" , " string" )
258- Assert .assertType (123 , " number" )
259- Assert .throws (() => Assert .assertType (123 , " string" ))
260- }, 3 ) // indent=3 (SUBSECTION)
231+ runner .exec (" Math Test" , () => TestCase .math (), 2 )
232+ runner .exec (" Null/Undefined Test" , () => TestCase .nullUndefined (), 2 )
233+ runner .exec (" Instance Test" , () => TestCase .instance (), 2 )
234+ runner .exec (" Throws/DoesNotThrow Test" , () => TestCase .throwsDoesNotThrow (), 2 )
235+ runner .exec (" Type Test" , () => TestCase .type (), 2 )
261236 success = true
262237 } finally {
263238 runner .title (success ? " All Tests Passed" : " Test Failure" , 1 )
264239 }
265240}
241+
242+ // Class to organize all test cases as static methods
243+ class TestCase {
244+ public static math() {
245+ Assert .equals (2 + 3 , 5 , " Addition works" )
246+ Assert .notEquals (2 * 2 , 5 , " Multiplication does not equal 5" )
247+ Assert .equals ([1 , 2 ], [1 , 2 ], " Array equality" )
248+ }
249+
250+ public static nullUndefined() {
251+ Assert .isNull (null , " Should be null" )
252+ Assert .isNotNull (0 , " Zero is not null" )
253+ Assert .isUndefined (undefined , " Should be undefined" )
254+ Assert .isNotUndefined (" " , " Empty string is defined" )
255+ Assert .isDefined (123 , " Number is defined" )
256+ }
257+
258+ public static instance() {
259+ class Animal {}
260+ class Dog extends Animal {}
261+ const d = new Dog ()
262+ Assert .isInstanceOf (d , Dog , " Dog instance of Dog" )
263+ Assert .isInstanceOf (d , Animal , " Dog instance of Animal" )
264+ Assert .throws (() => Assert .isInstanceOf ({}, Dog ), AssertionError , undefined , " Throws if not instance" )
265+ Assert .isNotInstanceOf ({}, Dog , " Plain object is not instance of Dog" )
266+ }
267+
268+ public static throwsDoesNotThrow() {
269+ // --- All throws cases ---
270+ // 1. Throws an Error with specific message
271+ Assert .throws (() => { throw new Error (" fail" ) }, Error , " fail" , " Should throw Error" )
272+
273+ // 2. Throws a TypeError
274+ Assert .throws (() => { throw new TypeError (" bad type" ) }, TypeError , " bad type" , " Should throw TypeError" )
275+
276+ // 3. Throws any error (not checking error type or message)
277+ Assert .throws (() => { throw " custom error string" }, undefined , undefined , " Should throw any error (string)" )
278+
279+ // 4. Throws AssertionError when an assertion fails inside
280+ Assert .throws (() => Assert .isTrue (false , " Forced fail" ), AssertionError , undefined , " Should throw AssertionError when assertion fails" )
281+
282+ // 5. Using a function variable that throws
283+ const failFunc = () => { throw new RangeError (" range fail" ) }
284+ Assert .throws (failFunc , RangeError , " range fail" , " Should throw RangeError" )
285+
286+ // --- All doesNotThrow cases ---
287+ // 1. Does not throw (simple value)
288+ Assert .doesNotThrow (() => 42 , " Should not throw on returning 42" )
289+
290+ // 2. Does not throw (returns undefined)
291+ Assert .doesNotThrow (() => undefined , " Should not throw on returning undefined" )
292+
293+ // 3. Does not throw (assertion that passes)
294+ Assert .doesNotThrow (() => Assert .isTrue (true , " Should pass" ), " Should not throw if assertion passes" )
295+
296+ // 4. Using a function variable that does not throw
297+ const safeFunc = () => " hello"
298+ Assert .doesNotThrow (safeFunc , " Should not throw with safeFunc" )
299+ }
300+
301+ public static type() {
302+ Assert .isType (" abc" , " string" , " abc is string" )
303+ Assert .isType (123 , " number" , " 123 is number" )
304+ Assert .throws (() => Assert .isType (123 , " string" ), undefined , undefined , " Throws if type mismatch" )
305+ Assert .isNotType (" hello" , " number" , " String is not number" )
306+ Assert .isNotType (42 , " string" , " Number is not string" )
307+ }
308+ }
309+
310+ // Make main available globally for Node/ts-node test environments
311+ if (typeof globalThis !== " undefined" && typeof main !== " undefined" ) {
312+ // @ts-ignore
313+ globalThis .main = main
314+ }
266315```
267316
268317---
@@ -279,12 +328,14 @@ function main(workbook: ExcelScript.Workbook) {
279328** END Instance Test **
280329** START Throws/DoesNotThrow Test **
281330** END Throws/DoesNotThrow Test **
331+ ** START Type Test **
332+ ** END Type Test **
282333* All Tests Passed *
283334```
284335
285336- Each title uses ` * ` characters as prefix/suffix, repeated according to the ` indent ` parameter.
286337- A title prints only if its ` indent ` is less than or equal to the runner's verbosity.
287- - Example above shows only indent 1 and 2 titles, because verbosity is set to ` SECTION ` (2 ).
338+ - Example above shows only indent ` 1 ` and ` 2 ` titles, because verbosity is set to ` SECTION ` (` 2 ` ).
288339
289340If verbosity level is ` HEADER ` the output will be:
290341```
@@ -303,6 +354,7 @@ If verbosity level is `HEADER` the output will be:
303354---
304355
305356## Additional Information
357+
306358- TypeDoc documentation: [ TYPEDOC] ( docs/typedoc/index.html )
307359
308360## License
0 commit comments