diff --git a/README.md b/README.md index 218a6c7..6eb546e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ - [Limitations](#limitations) - [API](#api) - [Example test app config and unit tests](#example-test-app-config-and-unit-tests) +- [Migration from v1 to v2](#migration-from-v1-to-v2) The unit testing framework works on top of the [Roku Unit Testing framework](https://github.com/rokudev/unit-testing-framework). There are some differences between those two frameworks. @@ -17,6 +18,7 @@ The unit testing framework works on top of the [Roku Unit Testing framework](htt We believe tests should be close to the tested objects. The expected structure of the app: + ``` components _mocks @@ -27,7 +29,9 @@ The expected structure of the app: MyComponent.xml MyService.brs ``` + The `_tests` folders should be placed near to the tested entity. Each test suite gains extra powers: + - no need for xml files - no need to define test suites functions in an array - ability to import dependencies @@ -69,6 +73,15 @@ Remark: You can use any name for the test environment, just be consistent. } ``` +4. \[Temporary\] To not force [migration from v1 to v2](#migration-from-v1-to-v2) to be imidiate we introduced a bs_const flag (details in Migration part). The flag will be temporary for the depreciation period. In your manifest file please add bs_const: +```js +{ + bs_const: { + insertKopytkoUnitTestSuiteArgument: false, + } +} +``` + ## Running Unit Tests Simply @@ -111,9 +124,9 @@ end function function MyServiceTestSuite() as Object ts = KopytkoTestSuite() - ts.setBeforeAll = sub (ts as Object) + beforeAll(sub (_ts as Object) ' do something - end sub + end sub) return ts end function @@ -124,8 +137,8 @@ function TestSuite__MyService_Main() as Object ts = MyServiceTestSuite() ts.name = "MyService - Main" - ts.addTest("it should create new instance of the service", function (ts as Object) as String - return ts.assertNotInvalid(MyService()) + it("should create new instance of the service", function (_ts as Object) as String + return expect(MyService()).toBeValid() end function) return ts @@ -137,7 +150,7 @@ function TestSuite__MyService_getData() as Object ts = MyServiceTestSuite() ts.name = "MyService - getData" - ts.addTest("it should return some data", function (ts as Object) as String + it("should return some data", function (_ts as Object) as String ' Given service = MyService() expected = { arg: "abc" } @@ -146,7 +159,7 @@ function TestSuite__MyService_getData() as Object result = service.getData("abc") 'Then - return ts.assertEqual(result, expected) + return expect(result).toEqual(expected) end function) return ts @@ -213,16 +226,52 @@ The service can be used like a regular object: data = service.getData("/test") ``` +When dependency is mocked (`@mock`). +You can use our `mockFunction` to set returned value of the mocked function. For example. + +```brs +it("should return mocked function value", function (_ts as Object) as String + ' Given + expected = 123 + mockFunction("functionName").returnedValue(expected) + + ' When + result = functionReturningFunctionNameResult() + + 'Then + return expect(result).toEqual(expected) +end function) +``` + +Or you can check if mocked function was called properly + +```brs +it("should call functionName once with argument a = 1", function (_ts as Object) as String + ' When + result = functionReturningFunctionNameResult() + + 'Then + return [ + expect("functionName").toHaveBeenCalledTimes(1), + expect("functionName").toHaveBeenCalledWith({ a: 1 }), + ] +end function) +``` + +[Here](docs/api/KopytkoMockFunction.md) are listed all `mockFunction` methods. + +There are also plenty of examples [here](/example/app/components/_tests/mockExamples.test.brs). + Calls to the methods or constructor can be inspected: ```brightscript -?m.__mocks.exampleService.getData.calls[0].params.arg -?m.__mocks.exampleService.constructorCalls[0].params.dependency +? mockFunction("ExampleService.getData").getCalls()[0].params.arg +? mockFunction("ExampleService").getConstructorCalls()[0].params.dependency ``` ## Setup and Teardown Roku Unit Testing Framework provides the way to execute your custom code before/after every test suite. -However, to give more flexibility, Kopytko Unit Testing Framework overwrites `setUp` and `tearDown` properties of a test suite, so you shouldn't use them. Instead, add your function via `setBeforeAll` or `setAfterAll` methods of `KopytkoTestSuite`. +However, to give more flexibility, Kopytko Unit Testing Framework overwrites `setUp` and `tearDown` properties of a test suite, so you shouldn't use them. Instead, add your function via `beforeAll` or `afterAll` methods of `KopytkoTestSuite`. `KopytkoFrameworkTestSuite` already contains some additional code to prepare and clean a test suite from Kopytko ecosystem related stuff. Notice that if you have test cases of a unit split into few files, every file creates a separate test suite, therefore all `beforeAll` and `afterAll` callbacks will be executed once per a file. @@ -238,7 +287,23 @@ Functions passed into all these methods and arrays should have just one `ts` arg - [KopytkoTestSuite](docs/api/KopytkoTestSuite.md) - [KopytkoFrameworkTestSuite](docs/api/KopytkoFrameworkTestSuite.md) +- [KopytkoTestFunctions](docs/api/KopytkoTestFunctions.md) +- [KopytkoExpect](docs/api/KopytkoExpect.md) +- [KopytkoMockFunction](docs/api/KopytkoMockFunction.md) ## Example test app config and unit tests Go to [/example](example) directory + +## Migration from v1 to v2 + +Version 2 introduces new shorthand functions and because of that, we were able to remove the test suite object argument from the test case function. + +Now if you want to get the `ts` (test suite) object, you can get it by calling the `ts()` function in a test case. + +In order to not make trouble for projects that already use v1, we introduced a **bs_const** flag [**insertKopytkoUnitTestSuiteArgument**](/example/manifest.js). So if you **don't want to change the current test cases implementation** add it to your manifest with the value **set to true**. + +When you want to use our new shorthand methods and you **don't need a test suite object argument**, as you don't use it, **set this flag to false**. + +**IMPORTANT: This flag is only temporary and will be removed in the future. +The desired solution is to not use the test suite argument.** diff --git a/docs/api/KopytkoExpect.md b/docs/api/KopytkoExpect.md new file mode 100644 index 0000000..910b538 --- /dev/null +++ b/docs/api/KopytkoExpect.md @@ -0,0 +1,267 @@ +# KopytkoExpect API + +Implementation of the `expect` function is a bit similar to the jestjs.io one. + +It can take any value, or mocked function name as an argument and you can call on it an assert function. + +- [Methods](#methods) + - [`toBe`](#tobe) + - [`toBeInvalid`](#tobeinvalid) + - [`toBeValid`](#tobevalid) + - [`toBeTrue`](#tobetrue) + - [`toBeFalse`](#tobefalse) + - [`toEqual`](#toequal) + - [`toContain`](#tocontain) + - [`toHaveKey`](#tohavekey) + - [`toHaveKeys`](#tohavekeys) + - [`toHaveLength`](#tohavelength) + - [`toHaveBeenCalled`](#tohavebeencalled) + - [`toHaveBeenCalledTimes`](#tohavebeencalledtimes) + - [`toHaveBeenCalledWith`](#tohavebeencalledwith) + - [`toHaveBeenLastCalledWith`](#tohavebeenlastcalledwith) + - [`toHaveBeenNthCalledWith`](#tohavebeennthcalledwith) + - [`toThrow`](#tothrow) + - [`not`](#not) + +## Methods + +### `toBe` + +Checks equality of two values, or Node reference. + +This does not work for object references (only Node). + +```brs +it("should pass", function (_ts as Object) as String + return expect(2).toBe(2) +end function) +``` + +### `toBeInvalid` + +Checks if value is invalid. + +```brs +it("should pass", function (_ts as Object) as String + return expect(Invalid).toBeInvalid() +end function) +``` + +### `toBeValid` + +Checks if value is valid. + +```brs +it("should pass", function (_ts as Object) as String + return expect(1).toBeValid() +end function) +``` + +### `toBeTrue` + +Checks if value is true. + +```brs +it("should pass", function (_ts as Object) as String + return expect(true).toBeTrue() +end function) +``` + +### `toBeFalse` + +Checks if value is false. + +```brs +it("should pass", function (_ts as Object) as String + return expect(false).toBeFalse() +end function) +``` + +### `toEqual` + +Checks values equality. For primitive values works the same as toBe. + +Examples: + +For AssociativeArray + +```brs +it("should pass", function (_ts as Object) as String + return expect({ a: 2 }).toEqual({ a: 2 }) +end function) +``` + +For Nodes + +```brs +it("should pass", function (_ts as Object) as String + nodeA = CreateNode("roSGNode", "Rectangle") + nodeA.id = "asd" + nodeB = CreateNode("roSGNode", "Rectangle") + nodeB.id = "asd" + + return expect(nodeA).toEqual(nodeB) +end function) +``` + +### `toContain` + +Checks if Array/AssociativeArray/Node contains the expected value/subset/node + +Examples: + +For Array entries + +```brs +it("should pass", function (_ts as Object) as String + return expect(["a", "b"]).toContain("a") +end function) +``` + +For AssociativeArray subset + +```brs +it("should pass", function (_ts as Object) as String + return expect({ a: 2, b: 5 }).toContain({ a: 2 }) +end function) +``` + +For Nodes fields + +```brs +it("should pass", function (_ts as Object) as String + node = CreateNode("roSGNode", "Rectangle") + node.id = "asd" + + return expect(node).toContain({ id: "asd" }) +end function) +``` + +For Nodes children + +```brs +it("should pass", function (_ts as Object) as String + parent = CreateNode("roSGNode", "Rectangle") + child = CreateNode("roSGNode", "Rectangle") + parent.appendChild(child) + + return expect(parent).toContain(child) +end function) +``` + +### `toHaveKey` + +Checks if AssociativeArray contains given key. + +```brs +it("should pass", function (_ts as Object) as String + return expect({ a: 2 }).toHaveKey("a") +end function) +``` + +### `toHaveKeys` + +Checks if AssociativeArray contains given keys. + +```brs +it("should pass", function (_ts as Object) as String + return expect({ a: 2, b: "asd" }).toHaveKeys(["a", "b"]) +end function) +``` + +### `toHaveLength` + +Checks if Array or AssociativeArray has given lenght/count. + +```brs +it("should pass", function (_ts as Object) as String + return expect({ a: 2, b: "asd" }).toHaveLength(2) +end function) +``` + +### `toHaveBeenCalled` + +Checks if mocked function was called at least once. + +```brs +' @mock /path/to/functionName + + +it("should pass", function (_ts as Object) as String + return expect("functionName").toHaveBeenCalled() +end function) +``` + +### `toHaveBeenCalledTimes` + +Checks if mocked function was called given times. + +```brs +' @mock /path/to/functionName + + +it("should pass", function (_ts as Object) as String + return expect("functionName").toHaveBeenCalledTimes(1) +end function) +``` + +### `toHaveBeenCalledWith` + +Checks if mocked function was called with given arguments. + +Checks if mocked function was called given times. + +```brs +' @mock /path/to/functionName + + +it("should pass", function (_ts as Object) as String + return expect("functionName").toHaveBeenCalledWith({ a: 2 }) +end function) +``` + +### `toHaveBeenLastCalledWith` + +Checks if mocked function last call was with given arguments. + +```brs +' @mock /path/to/functionName + + +it("should pass", function (_ts as Object) as String + return expect("functionName").toHaveBeenLastCalledWith({ a: 2 }) +end function) +``` + +### `toHaveBeenNthCalledWith` + +Checks if mocked function nth, given, call was executed with given arguments. + +```brs +' @mock /path/to/functionName + + +it("should pass", function (_ts as Object) as String + return expect("functionName").toHaveBeenNthCalledWith(1, { a: 2 }) +end function) +``` + +### `toThrow` + +Checks if the function throws an exception. + +```brs +it("should pass", function (_ts as Object) as String + return expect(functionThatThrow).toThrow("Error message") +end function) +``` + +### `not` + +Check the opposite of the assert function. + +```brs +it("should pass", function (_ts as Object) as String + return expect(1)not.toBe(4) +end function) +``` diff --git a/docs/api/KopytkoFrameworkTestSuite.md b/docs/api/KopytkoFrameworkTestSuite.md index 6c4dc01..7c93f69 100644 --- a/docs/api/KopytkoFrameworkTestSuite.md +++ b/docs/api/KopytkoFrameworkTestSuite.md @@ -1,24 +1,31 @@ # KopytkoFrameworkTestSuite API ## Methods -- [`assertDataWasSetOnStore`](#assertdatawassetonstore) -- [`assertRequestWasMade`](#assertrequestwasmade) + +- [Methods](#methods) +- [Reference](#reference) + - [`assertDataWasSetOnStore`](#assertdatawassetonstore) + - [`assertRequestWasMade`](#assertrequestwasmade) --- ## Reference ### `assertDataWasSetOnStore` + It checks if data was set on `Store` (part of [Roku Framework](https://github.com/getndazn/kopytko-framework)). Params: + - `data`: `Object` - the data that should be set on `Store` - `msg = ""`: `String` - additional message when test fails ### `assertRequestWasMade` + It checks if request was made via `createRequest` (part of [Roku Framework](https://github.com/getndazn/kopytko-framework)). Params: + - `params = {}`: `Object` - arguments passed to `createRequest` method - `options = {}`: `Object` - `times`: `Integer` - how many times it should be called diff --git a/docs/api/KopytkoMockFunction.md b/docs/api/KopytkoMockFunction.md new file mode 100644 index 0000000..5c7a363 --- /dev/null +++ b/docs/api/KopytkoMockFunction.md @@ -0,0 +1,108 @@ +# KopytkoMockFunction API + +The implementation of the `mockFunction` is a bit similar to the jestjs.io one. + +It takes mocked function name as an argument and you can call on it mock functions. + +The function name should be a string, if the function returns an object and you want to mock its methods you can use ".". + +```brs +mockFunction("functionName") +mockFunction("Service.serviceMethod") +``` + +- [Methods](#methods) + - [`clear`](#clear) + - [`getCalls`](#getcalls) + - [`getContructorCalls`](#getcontructorcalls) + - [`implementation`](#implementation) + - [`returnValue`](#returnvalue) + - [`resolvedValue`](#resolvedvalue) + - [`rejectedValue`](#rejectedvalue) + - [`throw`](#throw) + +## Methods + +### `clear` + +Clears all mock data set for this test case + +```brs +mockFunction("functionName").clear() +``` + +### `getCalls` + +Returns an array with all function mock calls, + +remember that all calls are cleared before each test case execution. + +```brs +calls = mockFunction("functionName").getCalls() +``` + +### `getContructorCalls` + +Returns an array with all function mock constructor calls, + +remember that all calls are cleared before each test case execution. + +```brs +contructorCalls = mockFunction("functionName").getContructorCalls() +``` + +### `implementation` + +It mocks function implementation + +Implementation function takes 2 arguments: +- params - object, where keys are names of the original implementation arguments, and values are arguments value +- context - the test suite context + +```brs +m.__expectedValue = 10 + +mockFunction("functionName").implementation(function (_params as Object, context as Object) as FunctionReturnType + return context.__expectedValue +end function) +``` + +### `returnValue` + +It mocks function return value + +```brs +calls = mockFunction("functionName").returnValue("no this will be returned by this funciton") +``` + +### `resolvedValue` + +For the convenience you can mock promise resolve value, +but for this, you need to import PromiseResolve from the kopytko-utils package. + +It mocks function promise resolved value + +```brs +mockFunction("functionName").resolvedValue(12) +``` + +### `rejectedValue` + +For the convenience you can mock promise reject value, +but for this, you need to import PromiseReject from the kopytko-utils package. + +It mocks function promise rejected value + +```brs +mockFunction("functionName").rejectedValue("error message") +``` + +### `throw` + +It mocks function exception. + +It can take a string or an error object. Exactly the same values you can pass to the throw statement. + +```brs +mockFunction("functionName").throw("Mocked error message") +``` diff --git a/docs/api/KopytkoTestFunctions.md b/docs/api/KopytkoTestFunctions.md new file mode 100644 index 0000000..fc71c7c --- /dev/null +++ b/docs/api/KopytkoTestFunctions.md @@ -0,0 +1,107 @@ +# KopytkoTestFunctions API + +- [Functions](#functions) + - [`it`](#it) + - [`itEach`](#iteach) + - [`beforeAll`](#beforeall) + - [`beforeEach`](#beforeeach) + - [`afterEach`](#aftereach) + - [`afterAll`](#afterall) + - [`ts`](#ts) + +In order to make developer live easier there are shorhands for all test function + +There is bs_const variable - **insertKopytkoUnitTestSuiteArgument** + +- when **true** all test case functions and hooks need ts (TestSuite) argument + +```brs +it("should check if true is true", function (_ts as Object) as String + return expect(true).toBeTrue() +end function) +``` + +- when **false** all test case functions and hooks do not need ts (TestSuite) argument + +```brs +it("should check if true is true", function () as String + return expect(true).toBeTrue() +end function) +``` + +## Functions + +### `it` + +This is a shorthand for the `ts.addTest`. + +It adds "it " to the beggining of thhe test name. + +```brs +it("should check if true is true", function (_ts as Object) as String + return expect(true).toBeTrue() +end function) +``` + +### `itEach` + +This is a shorthand for the `ts.addParameterizedTests`. + +It adds "it " to the beggining of thhe test name. + +```brs +itEach([ + { value: 2, expectedValue: 2 }, + { value: "asd", expectedValue: "asd" }, +], "should check if ${value} is ${expectedValue}", function (_ts as Object, params as Object) as String + return expect(params.value).toBe(params.expectedValue) +end function) +``` + +### `beforeAll` + +This is a shorthand for the `ts.setBeforeAll`. + +```brs +beforeAll(sub (_ts as Object) + ? "This will be printed before first test case execution" +end sub) +``` + +### `beforeEach` + +This is a shorthand for the `ts.setBeforeEach`. + +```brs +beforeEach(sub (_ts as Object) + ? "This will be printed before every test case execution" +end sub) +``` + +### `afterEach` + +This is a shorthand for the `ts.setAfterEach`. + +```brs +afterEach(sub (_ts as Object) + ? "This will be printed after every test case execution" +end sub) +``` + +### `afterAll` + +This is a shorthand for the `ts.setAfterAll`. + +```brs +afterAll(sub (_ts as Object) + ? "This will be printed after last test case execution" +end sub) +``` + +### `ts` + +Returns Test Suite object (ts). + +```brs +ts().isMatch({ a: 1 }, { a: 1 }) +``` diff --git a/docs/api/KopytkoTestSuite.md b/docs/api/KopytkoTestSuite.md index bdb1683..ad14bf4 100644 --- a/docs/api/KopytkoTestSuite.md +++ b/docs/api/KopytkoTestSuite.md @@ -1,6 +1,7 @@ # KopytkoTestSuite API ## Methods + - [`addParameterizedTests`](#addparameterizedtests) - [`assertNodesAreEqual`](#assertnodesareequal) - [`assertMethodWasCalled`](#assertmethodwascalled) @@ -11,6 +12,7 @@ - [`setBeforeEach`](#setbeforeeach) ## Callbacks + - [`setupOrTeardownCallback`](#setuporteardowncallback) --- @@ -18,7 +20,9 @@ ## Reference ### `addParameterizedTests` + Allows to test multiple parameters in a sequence. The `testName` string can be interpolated with values that implement `ifToStr` interface. For instance: + ```brightscript ts.addParameterizedTests([ 1, @@ -27,7 +31,9 @@ ts.addParameterizedTests([ return ts.assertTrue(param > 0) end function) ``` + or with object: + ```brightscript ts.addParameterizedTests([ { value: 1, expected: true }, @@ -38,6 +44,7 @@ end function) ``` Params: + - `paramsList`: `Object` - array of test values - `testName`: `String` - test description - `testFunction`: `Function` - takes the arguments: @@ -45,15 +52,19 @@ Params: - `param`: `Dynamic` - the single value from the array ### `assertNodesAreEqual` + Recursively checks if two given nodes are equal. This is not simple comparision of node references. The method deeply compares node's field values including their children. Params: + - `expected`: `Object` - node - `tested`: `Object` - node - `msg = ""`: `String` - additional message when test fails ### `assertMethodWasCalled` + It checks if the mocked method/function is called. It works with manual and automatic mocks. For example: + ```brightscript ' My tested entities function add(a as Integer, b as Integer) as Integer @@ -86,6 +97,7 @@ end function) ``` Params: + - `methodPath`: `String` - function or method name - `params = {}`: `Object` - function or method arguments - `options = {}`: `Object` @@ -93,11 +105,12 @@ Params: - `strict`: `Boolean` - affects how the params are compared. When `true` the params object must match the arguments types and values (1 to 1 equality). If `false` the params object can contain some of the passed arguments that are tested - `msg = ""`: `String` - additional message when test fails - ### `assertMethodWasNotCalled` + Opposite to `assertMethodWasCalled`. It checks if function/method was not called with given params or times. Params: + - `methodPath`: `String` - function or method name - `params = {}`: `Object` - function or method arguments - `options = {}`: `Object` @@ -106,12 +119,15 @@ Params: - `msg = ""`: `String` - additional message when test fails ### `setAfterAll` + Runs a function after all the tests in the file have completed. Params: + - `callback`: [`setupOrTeardownCallback`](#setuporteardowncallback) Example: + ```brightscript ts = KopytkoTestSuite() ts.setAfterAll(sub (ts as Object) @@ -120,23 +136,31 @@ end sub) ``` ### `setAfterEach` + Runs a function after each one of the tests in the file completes. Params: + - `callback`: [`setupOrTeardownCallback`](#setuporteardowncallback) ### `setBeforeAll` + Runs a function before all the tests in the file run. Params: + - `callback`: [`setupOrTeardownCallback`](#setuporteardowncallback) ### `setBeforeEach` + Runs a function before each one of the tests in the file runs. Params: + - `callback`: [`setupOrTeardownCallback`](#setuporteardowncallback) ### `setupOrTeardownCallback` + Params: + - `ts`: `Object` - the TestSuite object diff --git a/example/app/components/Math.brs b/example/app/components/Math.brs new file mode 100644 index 0000000..584d365 --- /dev/null +++ b/example/app/components/Math.brs @@ -0,0 +1,13 @@ +' @import /components/divide.brs + +function Math() as Object + prototype = {} + + prototype.multiply = function (valueA as Integer, valueB as Integer) as Integer + return valueA * valueB + end function + + prototype.divide = divide + + return prototype +end function diff --git a/example/app/components/_tests/Math.test.brs b/example/app/components/_tests/Math.test.brs new file mode 100644 index 0000000..20f80f0 --- /dev/null +++ b/example/app/components/_tests/Math.test.brs @@ -0,0 +1,33 @@ +' @import /components/KopytkoTestSuite.brs from @dazn/kopytko-unit-testing-framework +' @mock /components/divide.brs + +function TestSuite__Math() as Object + ts = KopytkoTestSuite() + ts.name = "Example - function returning object" + + it("should return a proper value for multiply", function () as String + ' When + result = Math().multiply(3, 4) + + ' Then + return expect(result).toBe(12) + end function) + + it("should properly invoke and return a mocked divide return value", function () as Object + ' Given + expected = 100 + mockFunction("divide").returnValue(expected) + + ' When + result = Math().divide(6, 3) + + ' Then + return [ + expect("divide").toHaveBeenCalledTimes(1), + expect("divide").toHaveBeenCalledWith({ valueA: 6, valueB: 3 }), + expect(result).toBe(expected), + ] + end function) + + return ts +end function diff --git a/example/app/components/_tests/expectExamples.test.brs b/example/app/components/_tests/expectExamples.test.brs index 9f51423..6f103f2 100644 --- a/example/app/components/_tests/expectExamples.test.brs +++ b/example/app/components/_tests/expectExamples.test.brs @@ -3,16 +3,12 @@ function TestSuite__expectExamples() as Object ts = KopytkoTestSuite() - - ts.setBeforeEach(sub (ts as Object) - m.__mocks = {} - m.__mocks.sum = {} - end sub) + ts.name = "Expect Examples" ' --------------------------------------------------------- ' toBeInvalid() ' --------------------------------------------------------- - ts.addTest("expect(received).toBeInvalid()", function (ts as Object) as String + it("expect(Invalid).toBeInvalid()", function () as String ' Given value = Invalid @@ -20,36 +16,36 @@ function TestSuite__expectExamples() as Object return expect(value).toBeInvalid() end function) - ts.addParameterizedTests([ - { value: 4 }, - { value: true }, - { value: "Test Value" }, - { value: { key: "value" } }, - { value: [1, 2, 3] }, - { value: CreateObject("roSGNode", "rectangle") }, - ], "expect(received).not.toBeInvalid()", function (ts as Object, params as Object) as String + itEach([ + 4, + true, + "Test Value", + { key: "value" }, + [1, 2, 3], + CreateObject("roSGNode", "rectangle"), + ], "expect(nonInvalidValue).not.toBeInvalid()", function (value as Object) as String ' Then - return expect(params.value).not.toBeInvalid() + return expect(value).not.toBeInvalid() end function) ' --------------------------------------------------------- ' toBeValid() ' --------------------------------------------------------- - ts.addParameterizedTests([ - { value: 4 }, - { value: true }, - { value: "Test Value" }, - { value: { key: "value" } }, - { value: [1, 2, 3] }, - { value: CreateObject("roSGNode", "rectangle") }, - ], "expect(received).toBeValid()", function (ts as Object, params as Object) as String + itEach([ + 4, + true, + "Test Value", + { key: "value" }, + [1, 2, 3], + CreateObject("roSGNode", "rectangle"), + ], "expect(nonInvalidValue).toBeValid()", function (value as Object) as String ' Then - return expect(params.value).toBeValid() + return expect(value).toBeValid() end function) - ts.addTest("expect(received).not.toBeValid()", function (ts as Object) as String + it("expect(Invalid).not.toBeValid()", function () as String ' Given value = Invalid @@ -60,7 +56,7 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toBeTrue() ' --------------------------------------------------------- - ts.addTest("expect(received).toBeTrue()", function (ts as Object) as String + it("expect(true).toBeTrue()", function () as String ' Given value = true @@ -68,7 +64,7 @@ function TestSuite__expectExamples() as Object return expect(value).toBeTrue() end function) - ts.addTest("expect(received).not.toBeTrue()", function (ts as Object) as String + it("expect(false).not.toBeTrue()", function () as String ' Given value = false @@ -79,7 +75,7 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toBeFalse() ' --------------------------------------------------------- - ts.addTest("expect(received).toBeFalse()", function (ts as Object) as String + it("expect(false).toBeFalse()", function () as String ' Given value = false @@ -87,7 +83,7 @@ function TestSuite__expectExamples() as Object return expect(value).toBeFalse() end function) - ts.addTest("expect(received).not.toBeFalse()", function (ts as Object) as String + it("expect(true).not.toBeFalse()", function () as String ' Given value = true @@ -98,17 +94,17 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toBe() ' --------------------------------------------------------- - ts.addParameterizedTests([ + itEach([ { value: 4, expectedValue: 4 }, { value: true, expectedValue: true }, { value: "Test Value", expectedValue: "Test Value" }, - ], "expect(received).toBe(expected)", function (ts as Object, params as Object) as String + ], "expect(${value}).toBe(${expectedValue})", function (params as Object) as String ' Then return expect(params.value).toBe(params.expectedValue) end function) - ts.addTest("expect(received).toBe(expected) for scenegraph node", function (ts as Object) as String + it("expect(nodeObjectReference).toBe(theSameNodeObjectReference) for scenegraph node", function () as String ' Given value = CreateObject("roSGNode", "rectangle") expectedValue = value @@ -117,12 +113,12 @@ function TestSuite__expectExamples() as Object return expect(value).toBe(expectedValue) end function) - ts.addParameterizedTests([ + itEach([ { value: 4, expectedValue: 5 }, { value: true, expectedValue: false }, { value: "Test Value", expectedValue: "Another Value" }, { value: CreateObject("roSGNode", "rectangle"), expectedValue: CreateObject("roSGNode", "rectangle") } - ], "expect(received).not.toBe(expected)", function (ts as Object, params as Object) as String + ], "expect(${value}).not.toBe(${expectedValue})", function (params as Object) as String ' Then return expect(params.value).not.toBe(params.expectedValue) @@ -131,20 +127,20 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toEqual() ' --------------------------------------------------------- - ts.addParameterizedTests([ + itEach([ { value: 4, expectedValue: 4 }, { value: true, expectedValue: true }, { value: "Test Value", expectedValue: "Test Value" }, { value: ["a", "b", "c"], expectedValue: ["a", "b", "c"] } { value: { key1: "value1", key2: "value2" }, expectedValue: { key1: "value1", key2: "value2" } } { value: CreateObject("roSGNode", "rectangle"), expectedValue: CreateObject("roSGNode", "rectangle") } - ], "expect(received).toEqual(expected)", function (ts as Object, params as Object) as String + ], "expect(value).toEqual(theSameValue)", function (params as Object) as String ' Then return expect(params.value).toEqual(params.expectedValue) end function) - ts.addTest("expect(received).toEqual(expected) for scenegraph node", function (ts as Object) as String + it("expect(node).toEqual(anotherNodeWithSameFieldsAndChildren) for scenegraph node", function () as String ' When value = CreateObject("roSGNode", "rectangle") value.id = "TestID" @@ -157,19 +153,19 @@ function TestSuite__expectExamples() as Object return expect(value).toEqual(expectedValue) end function) - ts.addParameterizedTests([ + itEach([ { value: 4, expectedValue: 5 }, { value: true, expectedValue: false }, { value: "Test Value", expectedValue: "Another Value" }, { value: ["a", "b", "c"], expectedValue: ["a", "b", "c", "d"] } { value: { key1: "value1", key2: "value2" }, expectedValue: { key1: "value1", key3: "value3" } } - ], "expect(received).not.toEqual(expected)", function (ts as Object, params as Object) as String + ], "expect(value).not.toEqual(notTheSameValue)", function (params as Object) as String ' Then return expect(params.value).not.toEqual(params.expectedValue) end function) - ts.addTest("expect(received).not.toEqual(expected)", function (ts as Object) as String + it("expect(node).not.toEqual(nodeWithDifferentFields)", function () as String ' Given value = CreateObject("roSGNode", "rectangle") value.id = "id_1" @@ -183,17 +179,17 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toContain() ' --------------------------------------------------------- - ts.addParameterizedTests([ + itEach([ { value: ["a", "b", "c"], expectedValue: "b" }, { value: ["a", "b", "c", "d"], expectedValue: ["b", "c"] }, { value: { key1: "value1", key2: "value2" }, expectedValue: {key2: "value2"} }, - ], "expect(received).toContain(expected)", function (ts as Object, params as Object) as String + ], "expect(object).toContain(valueOrFields)", function (params as Object) as String ' Then return expect(params.value).toContain(params.expectedValue) end function) - ts.addTest("expect(received).toContain(expected) to assert fields of scenegraph node", function (ts as Object) as String + it("expect(node).toContain(fields)", function () as String ' Given node = CreateObject("roSGNode", "rectangle") node.width = 100.0 @@ -203,7 +199,7 @@ function TestSuite__expectExamples() as Object return expect(node).toContain({ width: 100.0, height: 50.0 }) end function) - ts.addTest("expect(received).toContain(expected) to assert child nodes of scenegraph node", function (ts as Object) as String + it("expect(node).toContain(child)", function () as String ' Given parentNode = CreateObject("roSGNode", "rectangle") childNode = CreateObject("roSGNode", "rectangle") @@ -213,18 +209,18 @@ function TestSuite__expectExamples() as Object return expect(parentNode).toContain(childNode) end function) - ts.addParameterizedTests([ + itEach([ { value: ["a", "b", "c"], expectedValue: "d" }, { value: ["a", "b", "c", "d"], expectedValue: ["b", "e"] }, - { value: { key1: "value1", key2: "value2" }, expectedValue: {key3: "value3"} }, + { value: { key1: "value1", key2: "value2" }, expectedValue: { key3: "value3" } }, { value: CreateObject("roSGNode", "rectangle"), expectedValue: { someKey: "someValue" } } - ], "expect(received).not.toContain(expected)", function (ts as Object, params as Object) as String + ], "expect(object).not.toContain(valueOrFields)", function (params as Object) as String ' Then return expect(params.value).not.toContain(params.expectedValue) end function) - ts.addTest("expect(received).not.toContain(expected) to assert child nodes of scenegraph node", function (ts as Object) as String + it("expect(parentSGNode).not.toContain(childSGNode)", function () as String ' Given parentNode = CreateObject("roSGNode", "rectangle") childNode = CreateObject("roSGNode", "rectangle") @@ -234,47 +230,47 @@ function TestSuite__expectExamples() as Object end function) ' --------------------------------------------------------- - ' toHasKey() + ' toHaveKey() ' --------------------------------------------------------- - ts.addTest("expect(received).toHasKey(expected)", function (ts as Object) as String + it("expect({ key1: value1, key2: value2, key3: value3 }).toHaveKey(key2)", function () as String ' Given assArray = { key1: "value1", key2: "value2", key3: "value3" } ' Then - return expect(assArray).toHasKey("key2") + return expect(assArray).toHaveKey("key2") end function) - ts.addTest("expect(received).not.toHasKey(expected)", function (ts as Object) as String + it("expect({ key1: value1, key2: value2, key3: value3 }).not.toHaveKey(key4)", function () as String ' Given assArray = { key1: "value1", key2: "value2", key3: "value3" } ' Then - return expect(assArray).not.toHasKey("key4") + return expect(assArray).not.toHaveKey("key4") end function) ' --------------------------------------------------------- - ' toHasKeys() + ' toHaveKeys() ' --------------------------------------------------------- - ts.addTest("expect(received).toHasKeys(expected)", function (ts as Object) as String + it("expect({ key1: value1, key2: value2, key3: value3 }).toHaveKeys([key1, key2])", function () as String ' Given assArray = { key1: "value1", key2: "value2", key3: "value3" } ' Then - return expect(assArray).toHasKeys(["key1", "key2"]) + return expect(assArray).toHaveKeys(["key1", "key2"]) end function) - ts.addTest("expect(received).not.toHasKeys(expected)", function (ts as Object) as String + it("expect({ key1: value1, key2: value2, key3: value3 }).not.toHaveKeys([key1, key4])", function () as String ' Given assArray = { key1: "value1", key2: "value2", key3: "value3" } ' Then - return expect(assArray).not.toHasKeys(["key1", "key4"]) + return expect(assArray).not.toHaveKeys(["key1", "key4"]) end function) ' --------------------------------------------------------- ' toHaveLength() ' --------------------------------------------------------- - ts.addTest("expect(received).toHaveLength(expected)", function (ts as Object) as String + it("expect([value1, value2, value3]).toHaveLength(3)", function () as String ' Given arr = ["value1", "value2", "value3"] @@ -282,7 +278,7 @@ function TestSuite__expectExamples() as Object return expect(arr).toHaveLength(3) end function) - ts.addTest("expect(received).not.toHaveLength(expected)", function (ts as Object) as String + it("expect([value1, value2, value3]).not.toHaveLength(6)", function () as String ' Given arr = ["value1", "value2", "value3"] @@ -293,7 +289,7 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toHaveBeenCalled() ' --------------------------------------------------------- - ts.addTest("expect(received).toHaveBeenCalled()", function (ts as Object) as String + it("expect(funcCallingSum).toHaveBeenCalled()", function () as String ' When funcCallingSum() @@ -301,7 +297,7 @@ function TestSuite__expectExamples() as Object return expect("sum").toHaveBeenCalled() end function) - ts.addTest("expect(received).not.toHaveBeenCalled()", function (ts as Object) as String + it("expect(funcNotCallingSum).not.toHaveBeenCalled()", function () as String ' When funcNotCallingSum() @@ -312,7 +308,7 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toHaveBeenCalledTimes() ' --------------------------------------------------------- - ts.addTest("expect(received).toHaveBeenCalledTimes(expected)", function (ts as Object) as String + it("expect(funcCallingSum).toHaveBeenCalledTimes(2)", function () as String ' When funcCallingSum() funcCallingSum() @@ -321,7 +317,7 @@ function TestSuite__expectExamples() as Object return expect("sum").toHaveBeenCalledTimes(2) end function) - ts.addTest("expect(received).not.toHaveBeenCalledTimes(expected)", function (ts as Object) as String + it("expect(funcCallingSum).not.toHaveBeenCalledTimes(1)", function () as String ' When funcCallingSum() funcCallingSum() @@ -333,83 +329,77 @@ function TestSuite__expectExamples() as Object ' --------------------------------------------------------- ' toHaveBeenCalledWith() ' --------------------------------------------------------- - ts.addTest("expect(received).toHaveBeenCalledWith(expected)", function (ts as Object) as String + it("expect(funcCallingSum).toHaveBeenCalledWith({ a: 1, b: 2 })", function () as String ' When funcCallingSum(1, 2) ' Then - return expect("sum").toHaveBeenCalledWith({ a: 1, b: 2}) + return expect("sum").toHaveBeenCalledWith({ a: 1, b: 2 }) end function) - ts.addTest("expect(received).not.toHaveBeenCalledWith(expected)", function (ts as Object) as String + it("expect(funcCallingSum).not.toHaveBeenCalledWith({ a: 3, b: 2 })", function () as String ' When funcCallingSum(2, 3) ' Then - return expect("sum").not.toHaveBeenCalledWith({ a: 3, b: 2}) + return expect("sum").not.toHaveBeenCalledWith({ a: 3, b: 2 }) end function) ' --------------------------------------------------------- ' toHaveBeenLastCalledWith() ' --------------------------------------------------------- - ts.addTest("expect(received).toHaveBeenLastCalledWith(expected)", function (ts as Object) as String + it("expect(funcCallingSum).toHaveBeenLastCalledWith({ a: 5, b: 6 })", function () as String ' When funcCallingSum(1, 2) funcCallingSum(3, 4) funcCallingSum(5, 6) ' Then - return expect("sum").toHaveBeenLastCalledWith({ a: 5, b: 6}) + return expect("sum").toHaveBeenLastCalledWith({ a: 5, b: 6 }) end function) - ts.addTest("expect(received).not.toHaveBeenLastCalledWith(expected)", function (ts as Object) as String + it("expect(funcCallingSum).not.toHaveBeenLastCalledWith({ a: 3, b: 4 })", function () as String ' When funcCallingSum(1, 2) funcCallingSum(3, 4) funcCallingSum(5, 6) ' Then - return expect("sum").not.toHaveBeenLastCalledWith({ a: 3, b: 4}) + return expect("sum").not.toHaveBeenLastCalledWith({ a: 3, b: 4 }) end function) ' --------------------------------------------------------- ' toHaveBeenNthCalledWith() ' --------------------------------------------------------- - ts.addTest("expect(received).toHaveBeenNthCalledWith(expected)", function (ts as Object) as String + it("expect(funcCallingSum).toHaveBeenNthCalledWith(2, { a: 3, b: 4 })", function () as String ' When funcCallingSum(1, 2) funcCallingSum(3, 4) funcCallingSum(5, 6) ' Then - return expect("sum").toHaveBeenNthCalledWith(2, { a: 3, b: 4}) + return expect("sum").toHaveBeenNthCalledWith(2, { a: 3, b: 4 }) end function) - ts.addTest("expect(received).not.toHaveBeenNthCalledWith(expected)", function (ts as Object) as String + it("expect(funcCallingSum).not.toHaveBeenNthCalledWith(2, { a: 1, b: 2 })", function () as String ' When funcCallingSum(1, 2) funcCallingSum(3, 4) funcCallingSum(5, 6) ' Then - return expect("sum").not.toHaveBeenNthCalledWith(2, { a: 1, b: 2}) + return expect("sum").not.toHaveBeenNthCalledWith(2, { a: 1, b: 2 }) end function) ' --------------------------------------------------------- ' toThrow() ' --------------------------------------------------------- - ts.addTest("expect(received).toThrow()", function (ts as Object) as String - - return expect(function() - funcWithException() - end function).toThrow() + it("expect(funcWithException).toThrow()", function () as String + return expect(funcWithException).toThrow() end function) - ts.addTest("expect(received).not.toThrow()", function (ts as Object) as String - - return expect(function() - funcWithNoException() - end function).not.toThrow() + it("expect(funcWithNoException).not.toThrow()", function () as String + return expect(funcWithNoException).not.toThrow() end function) return ts diff --git a/example/app/components/_tests/failingTestsExamples.test.brs b/example/app/components/_tests/failingTestsExamples.test.brs new file mode 100644 index 0000000..eb42d86 --- /dev/null +++ b/example/app/components/_tests/failingTestsExamples.test.brs @@ -0,0 +1,74 @@ +' @import /components/KopytkoTestSuite.brs from @dazn/kopytko-unit-testing-framework +' @mock /components/divide.brs + +function TestSuite__failingTestsExamples() as Object + ts = KopytkoTestSuite() + ts.name = "Example - failing tests" + + it("should fail when assert fails", function () as String + return expect(1).toBe(3) + end function) + + it("should fail when one of asserts fails", function () as Object + return [ + expect(1).toBe(1), + expect(2).toBe(3), + expect(3).toBe(3), + ] + end function) + + it("should fail when all of asserts fail", function () as Object + return [ + expect(1).toBe(3), + expect(2).toBe(1), + expect(3).toBe(2), + ] + end function) + + it("should fail on try of checking calls of not mocked function", function () as Object + ' When + functionCallingSum(1, 2) + + ' Then + return [ + expect("sum").toHaveBeenCalledTimes(1), + expect("sum").toHaveBeenCalledWith({ a: 1, b: 2 }), + ] + end function) + + it("should fail on try of getting proper function result if function is mocked", function () as String + ' When + result = functionCallingDivide(6, 2) + + ' Then + return expect(result).toBe(3) + end function) + + it("should fail when try to compare arrays", function () as String + ' When + expected = ["a"] + result = expected + + ' Then + return expect(result).toBe(expected) + end function) + + it("should fail when try to compare associative arrays", function () as String + ' When + expected = { a: 1 } + result = expected + + ' Then + return expect(result).toBe(expected) + end function) + + it("should fail when illeagal operation is taken in the test", function () as String + ' When + nonexistingfunction(6, 2) + + ' Then + return expect(true).toBeTrue() + end function) + + return ts +end function diff --git a/example/app/components/_tests/mockExamples.test.brs b/example/app/components/_tests/mockExamples.test.brs new file mode 100644 index 0000000..e20fe50 --- /dev/null +++ b/example/app/components/_tests/mockExamples.test.brs @@ -0,0 +1,68 @@ +' @import /components/KopytkoTestSuite.brs from @dazn/kopytko-unit-testing-framework +' @mock /components/Math.brs +' @mock /components/sum.brs + +function TestSuite__mockExamples() as Object + ts = KopytkoTestSuite() + ts.name = "Mock Examples" + + beforeEach(sub () + mockFunction("sum").returnValue(111) + end sub) + + it("should return sum value passed in before each hook", function () as String + ' Then + return expect(funcCallingSum()).toBe(111) + end function) + + it("should be able to mock Math.multiply implementation", function () as String + ' When + m.__expected = 222 + mockFunction("Math.multiply").implementation(function (_params as Object, context as Object) as Integer + return context.__expected + end function) + + ' Then + return expect(funcCallingMultiply()).toBe(m.__expected) + end function) + + it("should be able to mock Math.multiply return value", function () as String + ' When + expected = 333 + mockFunction("Math.multiply").returnValue(expected) + + ' Then + return expect(funcCallingMultiply()).toBe(expected) + end function) + + it("should be able to mock Math.multiply error", function () as String + ' When + expected = "Some mocked error" + mockFunction("Math.multiply").throw(expected) + + ' Then + return expect(funcCallingMultiply).toThrow(expected) + end function) + + it("should be able to reset all previous mocks", function () as String + ' Given + mockFunction("Math.multiply").returnValue(444) + + ' When + mockFunction("Math.multiply").clear() + + ' Then + return expect(funcCallingMultiply()).toBe(0) + end function) + + ' This test needs to be at the bottom + it("should clear all mock calls from previous tests", function () as Object + ' Then + return [ + expect("Math.multiply").toHaveBeenCalledTimes(0), + expect(mockFunction("Math.multiply").getCalls()).toEqual([]), + ] + end function) + + return ts +end function diff --git a/example/app/components/_tests/sum.test.brs b/example/app/components/_tests/sum.test.brs index 85998df..30df4a5 100644 --- a/example/app/components/_tests/sum.test.brs +++ b/example/app/components/_tests/sum.test.brs @@ -1,8 +1,10 @@ ' @import /components/KopytkoTestSuite.brs from @dazn/kopytko-unit-testing-framework + function TestSuite__sum() as Object ts = KopytkoTestSuite() + ts.name = "Example - function" - ts.addTest("it should return a sum of two numbers", function (ts as Object) as String + it("should return a sum of two numbers", function () as String ' Given a = 1 b = 1 @@ -11,18 +13,18 @@ function TestSuite__sum() as Object result = sum(a, b) ' Then - return ts.assertEqual(result, 2) + return expect(result).toBe(2) end function) - ts.addParameterizedTests([ + itEach([ { a: 2, b: 4, expectedSum: 6 }, { a: 5, b: 10, expectedSum: 15 }, - ], "it should return ${expectedSum} when sum ${a} and ${b}", function (ts as Object, params as Object) as String + ], "should return ${expectedSum} when sum ${a} and ${b}", function (params as Object) as String ' When result = sum(params.a, params.b) ' Then - return ts.assertEqual(result, params.expectedSum) + return expect(result).toBe(params.expectedSum) end function) return ts diff --git a/example/app/components/divide.brs b/example/app/components/divide.brs new file mode 100644 index 0000000..c99c006 --- /dev/null +++ b/example/app/components/divide.brs @@ -0,0 +1,3 @@ +function divide(valueA as Integer, valueB as Integer) as Integer + return valueA / valueB +end function diff --git a/example/app/components/expectExamples.brs b/example/app/components/expectExamples.brs index 853ca38..ee94772 100644 --- a/example/app/components/expectExamples.brs +++ b/example/app/components/expectExamples.brs @@ -1,15 +1,15 @@ ' @import /components/sum.brs -function funcWithException() - throw "dummy exception!" -end function +sub funcWithException() + throw "dummy exception!" +end sub -function funcWithNoException() -end function +sub funcWithNoException() +end sub -function funcCallingSum(aValue = 1 as Integer, bValue = 2 as Integer) - value = sum(aValue, bValue) +function funcCallingSum(aValue = 1 as Integer, bValue = 2 as Integer) as Integer + return sum(aValue, bValue) end function -function funcNotCallingSum() -end function +sub funcNotCallingSum() +end sub diff --git a/example/app/components/failingTestsExamples.brs b/example/app/components/failingTestsExamples.brs new file mode 100644 index 0000000..de5ccfe --- /dev/null +++ b/example/app/components/failingTestsExamples.brs @@ -0,0 +1,10 @@ +' @import /components/sum.brs +' @import /components/divide.brs + +function functionCallingSum(a as Integer, b as Integer) as Integer + return sum(a, b) +end function + +function functionCallingDivide(a as Integer, b as Integer) as Integer + return divide(a, b) +end function diff --git a/example/app/components/mockExamples.brs b/example/app/components/mockExamples.brs new file mode 100644 index 0000000..6992340 --- /dev/null +++ b/example/app/components/mockExamples.brs @@ -0,0 +1,20 @@ +' @import /components/Math.brs +' @import /components/sum.brs + +function funcWithException() + throw "dummy exception!" +end function + +function funcWithNoException() +end function + +function funcCallingSum(aValue = 1 as Integer, bValue = 2 as Integer) as Integer + return sum(aValue, bValue) +end function + +function funcCallingMultiply(aValue = 1 as Integer, bValue = 2 as Integer) as Integer + return Math().multiply(aValue, bValue) +end function + +function funcNotCallingSum() +end function diff --git a/example/manifest.js b/example/manifest.js index af1ae69..18db4a3 100644 --- a/example/manifest.js +++ b/example/manifest.js @@ -3,4 +3,7 @@ const baseManifest = require('../manifest.js'); module.exports = { ...baseManifest, title: 'Kopytko Unit Testing Framework Example', + bs_const: { + insertKopytkoUnitTestSuiteArgument: false, + }, } diff --git a/src/components/KopytkoExpect.brs b/src/components/KopytkoExpect.brs index a6f66ef..8de1a10 100644 --- a/src/components/KopytkoExpect.brs +++ b/src/components/KopytkoExpect.brs @@ -7,8 +7,8 @@ ' toBeValid ' toContain ' toEqual -' toHasKey -' toHasKeys +' toHaveKey +' toHaveKeys ' toHaveLength ' toHaveBeenCalled ' toHaveBeenCalledTimes @@ -135,7 +135,7 @@ function expect(value as Dynamic) as Object ' ---------------------------------------------------------------- ' To ensure if Array/AssociativeArray/node contains the expected value/subset/node ' - ' @param value (dynamic) - Expected value + ' @param value (dynamic) - Expected value ' ' @return Empty string (if contains) OR an error message ' ---------------------------------------------------------------- @@ -189,7 +189,7 @@ function expect(value as Dynamic) as Object ' ' @return Empty string (if contains) OR an error message ' ---------------------------------------------------------------- - context.toHasKey = function (key as Dynamic) as String + context.toHaveKey = function (key as Dynamic) as String MATCHER_NAME = "toHaveKey(expected)" errorMsg = m._matcherErrorMessage(MATCHER_NAME, key, m._received, { isNot : m._isNot }) @@ -208,7 +208,7 @@ function expect(value as Dynamic) as Object ' ' @return Empty string (if contains) OR an error message ' ---------------------------------------------------------------- - context.toHasKeys = function (keys as Object) as String + context.toHaveKeys = function (keys as Object) as String MATCHER_NAME = "toHaveKeys(expected)" errorMsg = m._matcherErrorMessage(MATCHER_NAME, keys, m._received, { isNot : m._isNot }) @@ -283,6 +283,7 @@ function expect(value as Dynamic) as Object methodMock = m._ts.getProperty(GetGlobalAA().__mocks, m._received, { calls: [] }) methodMockCalls = [] callsParams = [] + passed = false if (TF_Utils__IsValid(methodMock.calls)) methodMockCalls = methodMock.calls @@ -396,23 +397,37 @@ function expect(value as Dynamic) as Object ' ' @return empty string (if throws) OR an error message ' ---------------------------------------------------------------- - context.toThrow = function () + context.toThrow = function (expectedError = Invalid as Dynamic) as String ' return error if received value is not a function if (NOT TF_Utils__IsFunction(m._received)) then return "Received value must be a function" - MATCHER_NAME = "toThrow()" + MATCHER_NAME = "toThrow([error])" passed = false + expected = m._ts.ternary(expectedError = Invalid, "throws", expectedError) + received = Invalid try m._received() - catch e - passed = true + catch error + if (expectedError <> Invalid) + if (m._ts.getType(expectedError) = "roString") + passed = (expectedError = error.message) + received = error.message + else if (m._ts.getType(expectedError) = "roAssociativeArray") + passed = m._ts.isMatch(expectedError, error) + received = error + else + return "The received error is not a String nor an AssociativeArray" + end if + else + passed = true + end if end try ' if matcher has been called with expect.not passed = m._ts.ternary(m._isNot, NOT passed, passed) - return m._ts.ternary(passed, "", m._matcherErrorMessage(MATCHER_NAME, "throws", e, { isNot : m._isNot })) + return m._ts.ternary(passed, "", m._matcherErrorMessage(MATCHER_NAME, expected, received, { isNot : m._isNot })) end function ' ---------------------------------------------------------------- @@ -452,19 +467,19 @@ function expect(value as Dynamic) as Object end if end function - context._isArray = function (value as Dynamic) as boolean + context._isArray = function (value as Dynamic) as Boolean return TF_Utils__IsValid(value) AND m._ts.getType(value) = "roArray" end function - context._isAssociativeArray = function (value as Dynamic) as boolean + context._isAssociativeArray = function (value as Dynamic) as Boolean return TF_Utils__IsValid(value) AND m._ts.getType(value) = "roAssociativeArray" end function - context._isSGNode = function (value as Dynamic) as boolean + context._isSGNode = function (value as Dynamic) as Boolean return TF_Utils__IsValid(value) AND m._ts.getType(value) = "roSGNode" end function - context._isList = function (value as Dynamic) as boolean + context._isList = function (value as Dynamic) as Boolean return TF_Utils__IsValid(value) AND m._ts.getType(value) = "roList" end function diff --git a/src/components/KopytkoMockFunction.brs b/src/components/KopytkoMockFunction.brs new file mode 100644 index 0000000..9c074c3 --- /dev/null +++ b/src/components/KopytkoMockFunction.brs @@ -0,0 +1,116 @@ +function mockFunction(functionName as String) as Object + _globalAA = GetGlobalAA() + context = {} + context._functionName = functionName + context._ts = _globalAA["$$ts"] + + if (_globalAA["__mocks"] = Invalid) + _globalAA["__mocks"] = {} + end if + + context._mock = _globalAA["__mocks"] + for each part in functionName.split(".") + if (context._mock[part] = Invalid) + context._mock[part] = {} + end if + + context._mock = context._mock[part] + end for + + ' ---------------------------------------------------------------- + ' To clear mock settings and calls + ' ---------------------------------------------------------------- + context.clear = sub () + m._mock.clear() + end sub + + ' ---------------------------------------------------------------- + ' Returns mock calls + ' + ' @return An array with function calls + ' ---------------------------------------------------------------- + context.getCalls = function () as Object + return m._ts.getProperty(GetGlobalAA()["__mocks"], m._functionName + ".calls", []) + end function + + ' ---------------------------------------------------------------- + ' Returns mock constructor calls + ' + ' @return An array with function calls + ' ---------------------------------------------------------------- + context.getContructorCalls = function () as Object + return m._ts.getProperty(GetGlobalAA()["__mocks"], m._functionName + ".constructorCalls", []) + end function + + ' ---------------------------------------------------------------- + ' Mocks function implementation + ' + ' @param func (function) - Function implementation + ' This function has 2 params + ' - params - object where keys are argument names used in original implementation + ' - context - context of the test suite, where you can store some data hany in the funciton implementation + ' ---------------------------------------------------------------- + context.implementation = sub (func as Function) + m._mock.getReturnValue = func + end sub + + ' ---------------------------------------------------------------- + ' Mocks function return value + ' + ' @param value (dynamic) - Expected value + ' ---------------------------------------------------------------- + context.returnValue = sub (value as Dynamic) + m._mock.returnValue = value + end sub + + ' ---------------------------------------------------------------- + ' Mocks function resolved value + ' + ' @param value (dynamic) - Expected value + ' ---------------------------------------------------------------- + context.resolvedValue = sub (value as Dynamic) + if (PromiseResolve <> Invalid) + m._mock.returnValue = PromiseResolve(value) + else + error = [ + "To mock resolvedValue you need to import PromiseResolve from @dazn/kopytko-utils in your test", + "Please add the following line to your test", + "' @import /components/promise/PromiseResolve.brs from @dazn/kopytko-utils", + ] + throw error.join(Chr(10)) + end if + end sub + + ' ---------------------------------------------------------------- + ' Mocks function rejected value + ' + ' @param error (dynamic) - Expected error + ' ---------------------------------------------------------------- + context.rejectedValue = sub (error as Dynamic) + if (PromiseReject <> Invalid) + m._mock.returnValue = PromiseReject(error) + else + error = [ + "To mock rejectedValue you need to import PromiseReject from @dazn/kopytko-utils in your test", + "Please add the following line to your test", + "' @import /components/promise/PromiseReject.brs from @dazn/kopytko-utils", + ] + throw error.join(Chr(10)) + end if + end sub + + ' ---------------------------------------------------------------- + ' Mocks function exception + ' + ' @param error (dynamic) - Expected error String or Object + ' https://developer.roku.com/docs/references/brightscript/language/error-handling.md#throwing-exceptions + ' ---------------------------------------------------------------- + context.throw = sub (error as Dynamic) + m._mock["$$errorToThrow"] = error + m._mock.getReturnValue = function (_params as Object, _m as Object) as Dynamic + throw m["$$errorToThrow"] + end function + end sub + + return context +end function diff --git a/src/components/KopytkoTestFunctions.brs b/src/components/KopytkoTestFunctions.brs new file mode 100644 index 0000000..2d6f604 --- /dev/null +++ b/src/components/KopytkoTestFunctions.brs @@ -0,0 +1,50 @@ +' ---------------------------------------------------------------- +' Calls ts.addTest with the same arguments, +' adds "it " to the beginning of the test name +' ---------------------------------------------------------------- +sub it(testName as String, func as Function) + ts().addTest("it " + testName, func) +end sub + +' ---------------------------------------------------------------- +' Calls ts.addParameterizedTests with the same arguments, +' adds "it " to the beginning of the test name +' ---------------------------------------------------------------- +sub itEach(paramsList as Object, testName as String, func as Function) + ts().addParameterizedTests(paramsList, "it " + testName, func) +end sub + +' ---------------------------------------------------------------- +' Calls ts.setBeforeAll with the same arguments +' ---------------------------------------------------------------- +sub beforeAll(callback as Function) + ts().setBeforeAll(callback) +end sub + +' ---------------------------------------------------------------- +' Calls ts.setBeforeEach with the same arguments +' ---------------------------------------------------------------- +sub beforeEach(callback as Function) + ts().setBeforeEach(callback) +end sub + +' ---------------------------------------------------------------- +' Calls ts.setAfterEach with the same arguments +' ---------------------------------------------------------------- +sub afterEach(callback as Function) + ts().setAfterEach(callback) +end sub + +' ---------------------------------------------------------------- +' Calls ts.setAfterAll with the same arguments +' ---------------------------------------------------------------- +sub afterAll(callback as Function) + ts().setAfterAll(callback) +end sub + +' ---------------------------------------------------------------- +' Returns test suite object +' ---------------------------------------------------------------- +function ts() as Object + return m["$$ts"] +end function diff --git a/src/components/KopytkoTestSuite.brs b/src/components/KopytkoTestSuite.brs index fe11f6d..569334c 100644 --- a/src/components/KopytkoTestSuite.brs +++ b/src/components/KopytkoTestSuite.brs @@ -3,35 +3,55 @@ ' @import /components/_testUtils/NodeUtils.brs ' @import /components/_testUtils/ternary.brs ' @import /components/KopytkoExpect.brs +' @import /components/KopytkoMockFunction.brs +' @import /components/KopytkoTestFunctions.brs ' @import /source/UnitTestFramework.brs function KopytkoTestSuite() as Object ts = BaseTestSuite() GetGlobalAA()["$$ts"] = ts + ts._ERROR_MESSAGE_LINE_BREAK = Chr(10) + "--- " + ' @protected ts._beforeAll = [] ' @protected ts._afterAll = [] ' @protected - ts._beforeEach = [sub (ts as Object) - m.__mocks = {} - end sub] + ts._beforeEach = [] ' @protected ts._afterEach = [] + #if insertKopytkoUnitTestSuiteArgument + ts._beforeEach.push(sub (_ts as Object) + m.__mocks = {} + end sub) + #else + ts._beforeEach.push(sub () + m.__mocks = {} + end sub) + #end if + ts.setUp = sub () - for each beforeAll in m._beforeAll + for each _beforeAll in m._beforeAll if (TF_Utils__IsFunction(beforeAll)) - beforeAll(m) + #if insertKopytkoUnitTestSuiteArgument + _beforeAll(m) + #else + _beforeAll() + #end if end if end for end sub ts.tearDown = sub () - for each afterAll in m._afterAll + for each _afterAll in m._afterAll if (TF_Utils__IsFunction(afterAll)) - afterAll(m) + #if insertKopytkoUnitTestSuiteArgument + _afterAll(m) + #else + _afterAll() + #end if end if end for end sub @@ -59,35 +79,65 @@ function KopytkoTestSuite() as Object end sub ' "func as Function" crashes the app - ts.createTest = function (name as String, func as Object, setup = invalid as Object, teardown = invalid as Object, arg = invalid as Dynamic, hasArgs = false as Boolean, skip = false as Boolean) as Object + ts.createTest = function (name as String, func as Object, setup = invalid as Object, teardown = invalid as Object, arg = invalid as Dynamic, hasArgs = false as Boolean, _skip = false as Boolean) as Object return { Name: name, _func: [func], Func: function () as String ' TestRunner runs this method within TestSuite context - for each beforeEach in m._beforeEach - if (TF_Utils__IsFunction(beforeEach)) - beforeEach(m) + for each _beforeEach in m._beforeEach + if (TF_Utils__IsFunction(_beforeEach)) + #if insertKopytkoUnitTestSuiteArgument + _beforeEach(m) + #else + _beforeEach() + #end if end if end for try if (m.testInstance.hasArguments) - result = m.testInstance._func[0](m, m.testInstance.arg) + #if insertKopytkoUnitTestSuiteArgument + result = m.testInstance._func[0](m, m.testInstance.arg) + #else + result = m.testInstance._func[0](m.testInstance.arg) + #end if else - result = m.testInstance._func[0](m) + #if insertKopytkoUnitTestSuiteArgument + result = m.testInstance._func[0](m) + #else + result = m.testInstance._func[0]() + #end if end if catch e sourceObj = e.backtrace[e.backtrace.count() - 1] result = Substitute("{0} at {1}", e.message, FormatJson(sourceObj)) end try - for each afterEach in m._afterEach - if (TF_Utils__IsFunction(afterEach)) - afterEach(m) + for each _afterEach in m._afterEach + if (TF_Utils__IsFunction(_afterEach)) + #if insertKopytkoUnitTestSuiteArgument + _afterEach(m) + #else + _afterEach() + #end if end if end for + if (Type(result) = "roArray") + if (result.join("") <> "") + notPassedResults = [] + + for each assertResult in result + if (assertResult <> "") then notPassedResults.push(assertResult) + end for + + result = notPassedResults.join(m._ERROR_MESSAGE_LINE_BREAK) + else + result = "" + end if + end if + return result end function, _setUp: [setup],