https://github.com/getndazn/kopytko-unit-testing-framework
Extended Roku's Unit Testing Framework with additional assert functionalities and the mocking mechanism.
https://github.com/getndazn/kopytko-unit-testing-framework
kopytko roku unit-testing
Last synced: 5 months ago
JSON representation
Extended Roku's Unit Testing Framework with additional assert functionalities and the mocking mechanism.
- Host: GitHub
- URL: https://github.com/getndazn/kopytko-unit-testing-framework
- Owner: getndazn
- License: mit
- Created: 2021-07-08T13:32:39.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2024-02-06T13:50:36.000Z (over 2 years ago)
- Last Synced: 2025-11-23T03:11:50.932Z (7 months ago)
- Topics: kopytko, roku, unit-testing
- Language: Brightscript
- Homepage:
- Size: 568 KB
- Stars: 4
- Watchers: 61
- Forks: 3
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Roadmap: docs/ROADMAP.md
Awesome Lists containing this project
README
# Kopytko Unit Testing Framework
- [App Structure](#app-structure)
- [Setup](#setup)
- [Running Unit Tests](#running-unit-tests)
- [Kopytko Unit Test Philosophy](#kopytko-unit-test-philosophy)
- [Test Mocks](#test-mocks)
- [Setup and Teardown](#setup-and-teardown)
- [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.
## App Structure
We believe tests should be close to the tested objects.
The expected structure of the app:
```
components
_mocks
MyService.mock.brs
_tests
MyComponent.test.brs
MyComponent.brs
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
- ability to mock dependencies automatically
- ability to mock dependencies manually
## Setup
1. Install framework as a dev dependency
```shell
npm install @dazn/kopytko-unit-testing-framework --save-dev
```
2. Kopytko Unit Testing Framework uses [Kopytko Packager](https://github.com/getndazn/kopytko-packager) to build apps.
If you don't use it yet, go to its docs and init a @kopytko app. Once done, setup test environment in your `.kopytkorc` file
```json
{
"pluginDefinitions": {
"generate-tests": "/node_modules/@dazn/kopytko-unit-testing-framework/plugins/generate-tests"
},
"plugins": [
{ "name": "kopytko-copy-external-dependencies", "preEnvironmentPlugin": true }
],
"environments": {
"test": {
"plugins": ["generate-tests"]
}
}
}
```
Remark: You can use any name for the test environment, just be consistent.
3. Setup test script in your `package.json`
```json
{
"scripts": {
"test": "ENV=test node ../scripts/test.js"
}
}
```
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
```shell
npm test
```
If you want to run unit tests of a specific unit, you can pass the file name as a default argument:
```shell
npm test -- MyTestableUnit
```
This is a shortcut for `npm test -- --testFileName=MyTestableUnit`
## Kopytko Unit Test Philosophy
The unit tests can be split into multiple files and imported by the packager automatically. Let's consider the following example:
```
components
_tests
MyService_getData.test.brs
MyService_Main.test.brs
MyServiceTestSuite.test.brs
MyService.brs
```
`MyService.brs`
```brightscript
function MyService() as Object
prototype = {}
prototype.getData = function (arg as String) as Object
return { arg: arg }
end function
return prototype
end function
```
`MyServiceTestSuite.test.brs`
```brightscript
' @import /components/KopytkoTestSuite.brs from @dazn/kopytko-unit-testing-framework
function MyServiceTestSuite() as Object
ts = KopytkoTestSuite()
beforeAll(sub (_ts as Object)
' do something
end sub)
return ts
end function
```
`MyService_Main.test.brs`
```brightscript
function TestSuite__MyService_Main() as Object
ts = MyServiceTestSuite()
ts.name = "MyService - Main"
it("should create new instance of the service", function (_ts as Object) as String
return expect(MyService()).toBeValid()
end function)
return ts
end function
```
`MyService_getData.test.brs`
```brightscript
function TestSuite__MyService_getData() as Object
ts = MyServiceTestSuite()
ts.name = "MyService - getData"
it("should return some data", function (_ts as Object) as String
' Given
service = MyService()
expected = { arg: "abc" }
' When
result = service.getData("abc")
'Then
return expect(result).toEqual(expected)
end function)
return ts
end function
```
Such structure is understood and imported automatically by the packager.
Behind the scenes Kopytko Unit Testing Framework replaces the source/Main.brs file to run unit tests.
Roku's Unit Testing Framework core file is automatically imported by Kopytko Packager via ROPM.
## Test Mocks
Dependencies may be mocked by using `@mock` annotation after dependencies import in the main test file:
`' @mock pkg:/components/example/ExampleService.brs`
Type of dependency will be automatically recognized and proper function or object mock will be automatically generated on the fly (during the build process). There are 3 different types:
- object that implements methods
- function
- node
In case that auto-generated mock doesn't suit our needs, manual mock may be created. It has to be created under `_mocks` directory located next to the mocked file. Manual mock file has to be named after the mocked element with `.mock.brs` postfix e.g. `GlobalNode.mock.brs` for the `GlobalNode.brs` element.
It's also possible to create a common config for the mock. To do so, a file named after the mocked element with `.config.brs` postfix has to be added in the `_mocks` directory. Such config will be automatically imported when a dependency is mocked by the `@mock` annotation.
Example `ExampleService.brs`:
```brightscript
function ExampleService(dependency as Object) as Object
prototype = {}
prototype.getData = function (arg as String) as Object
return { someKey: "someValue" }
end function
return prototype
end function
```
`ExampleService.mock.brs`:
```brightscript
function ExampleService(dependency as Object) as Object
return Mock({
testComponent: m,
name: "ExampleService",
methods: {
getData: function (arg as Object) as Object
return m.getDataMock("getData", { arg: arg })
end function,
},
})
end function
```
In the unit tests the special field `__mocks` will be created and configuration can be added:
```brightscript
m.__mocks.exampleService = {
getData: {
returnValue: 1,
},
}
```
The service can be used like a regular object:
```brightscript
service = ExampleService(dependency)
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
? 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 `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.
`KopytkoTestSuite` provides additional possibility to run custom code before/after every test suite via `setBeforeEach` and `setAfterEach`
methods.
Functions passed into all these methods and arrays should have just one `ts` argument which is a test suite.
## Limitations
- The Framework was not tested with the annotations
## API
- [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.**