Skip to content

Unit testing guide #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: source
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions docs/advanced/automated-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
---
title: Unit Testing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title: Unit Testing
title: Unit Testing With Busted

---

## Getting started

This guide is going to explore unit testing, it’s assuming some familiarity with the subject, but it’s not required.

So to start off, you’re gonna need some project set up, the specifics don’t really matter, you can just take these ideas and extrapolate them to your own needs, but for demonstration I’m going to have a basic project set up like this:

![Project setup](/images/testing-project-setup.png)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Text instead e.g.

- dirOne
- dirTwo
   - file1
...

Or a descriptive Alt text:

"Directory structure showing Directory X with Files X,Y,Z"


Our code is very simple for this example.

```typescript title=index.ts
export const libraryFunction = (param: number[], reverse: boolean) => {
let reversed: number[] = [];

if (reverse) {
param.forEach((n) => table.insert(reversed, 1, n));
} else {
reversed = param;
}

return table.concat(reversed, ",");
};
```

So our function takes an array of numbers like “[1,2,3,4]” and returns a string like “1,2,3,4” or “4,3,2,1”.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
So our function takes an array of numbers like [1,2,3,4] and returns a string like “1,2,3,4” or “4,3,2,1”.
So our function takes an array of numbers like `[1,2,3,4]` and returns a string like “1,2,3,4” or “4,3,2,1”.


This is where unit testing comes in, say a piece of code depends on this function working like it does, and for it to keep working exactly like it does right now, obviously you could use the tried and true method of doing the testing yourself, but unit testing provides an alternative to the manual work.

So, unit testing, where do we begin?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Start a new section here titled 'Unit tests with Busted'


To start off we’re going to need these tools installed:

https://luarocks.org/
&
https://olivinelabs.com/busted/

_Luarocks is known to be hard to impossible to install on Windows systems sucessfully, if that’s the case for you, definitely keep reading on, we’re gonna explore a (free) option to do away with running tests locally entirely._

Now that we have busted installed and the `busted` command runs in your terminal of choice successfully we can proceed.

Let’s quickly install busted typings like so `npm install -d busted-tstl`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Let’s quickly install busted typings like so `npm install -d busted-tstl`
Install busted type declarations into your project using `npm install -d busted-tstl`


And we need to add busted-tstl types to our tsconfig.json, you should already have a section like this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only required in the tsconfig in your test directory, also in your example repository I would suggest moving tsconfig.test.json to spec/tsconfig.json, so you are not polluting your main codebase with testing stuff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not assume people already have types in their tsconfig, and I think it would be a good idea to mention compilerOptions, like we do on the language extensions page.


```json title=tsconfig.json
"types": [
"typescript-to-lua/language-extensions",
"lua-types/jit",
]
```

Simply add `"busted-tstl"` :

```json title=tsconfig.json
"types": [
"typescript-to-lua/language-extensions",
"lua-types/jit",
"busted-tstl"
]
```

Now let’s set up a folder for our tests, by default busted takes a folder named `spec` and runs the files in there, now
this is personal preference, but usually I name my tests `[filename to be tested]_spec.ts`, the \_spec suffix is once again what busted searches for by default, so we’ll stick to that.

Now our project should look something like this:

![Project setup](/images/testing-project-setup-with-spec-folder.png)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use text instead if possible (see above)
If not provide a accurate alt-text description e.g: "File Explorer of vscode showing directory X, File Y... with a red border around File Y..."


Alright, we can start writing our first test. Let’s explore the following example briefly:

```typescript title=index_spec.ts
import { libraryFunction } from "..";

describe("Library Function", () => {
it("Returns unreversed string correctly", () => {
assert.is_equal("1,2,3,4", libraryFunction([1, 2, 3, 4], false));
});
});
```

So the short version of what’s happening there is the ‘describe’ block is our named collection of tests, this collection tests the library function, so we've named it `"Library Function"`

Next we call it(‘’,()=>{}) in there to name and describe the behaviour of a single test, it’s a regular function, so you can call and do anything in there. Here, we’re just using a function in the assert namespace which passes the test if the 2 parameters are exactly equal, and fails if they’re different.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Next we call it(‘’,()=>{}) in there to name and describe the behaviour of a single test, it’s a regular function, so you can call and do anything in there. Here, we’re just using a function in the assert namespace which passes the test if the 2 parameters are exactly equal, and fails if they’re different.
Next we call `it(‘’,()=>{})` in there to name and describe the behaviour of a single test, it’s a regular function, so you can call and do anything in there. Here, we’re just using a function in the assert namespace which passes the test if the 2 parameters are exactly equal, and fails if they’re different.

(Added preformat/code backticks)


Alright, so we call our libraryFunction with a set of parameters, and get back some return, then we assert that the return value is equal to a known (correct) value. For illustration let’s add another test, since we have a second signature of the function that returns a reversed string, we can try that:

```typescript title=index_spec.ts
import { libraryFunction } from "..";

describe("Library Function", () => {
it("Returns unreversed string correctly", () => {
assert.is_equal("1,2,3,4", libraryFunction([1, 2, 3, 4], false));
});
it("Returns reversed string correctly", () => {
assert.is_equal("4,3,2,1", libraryFunction([1, 2, 3, 4], true));
});
});
```

Now hit compile and change your terminals directory in to your tstl output folder, for me it’s the `dist` folder.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to set this up as part of your TS workspace. To do this you can specify a custom 'test' script in package.json that builds and executes tests. An example of how I would set up my package.json in project root for your example repo:

{
    ...
    "scripts": {
        ...
        "pretest": "tstl -p spec",
        "test": "busted spec",
        ...
    }
}

This would mean you no longer need to switch between directories. It also means you can build and execute your tests by simply running npm test, which is the standard way of running tests for node projects.

Have a look at the TypeScriptToLua package.json as an example:

We can now just simply run `busted`, and you should see something like this:

![Project setup](/images/busted-output.png)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide a more accurate alt text actually describing the contents of the image.


2 tests succeeded, so our code is working as expected for these 2 scenarios, of course these aren’t exhaustive, for some real functionality they would be more exhaustive, more scenarios and validation, but this should set you on your tracks of busted testing your code.

Now we can move on to the next topic

## Automating the testing

For this we’re going to use the free tools provided by GitHub to every public(and most private?) repository, this assumes you already have a git repository hosted on GitHub, if you need help doing that I can point you to their exhaustive docs:

https://docs.github.com/en/get-started/quickstart

And also the documentation about actions specifically:

https://docs.github.com/en/actions

So, first we’re going to create a workflow file

Set up a folder structure like so:

```
Root project folder/
.github/
workflows/
testing.yml
```

```yaml title=testing.yml
name: TSTL Testing

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build_tests:
name: Busted Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Use Node.js [16.x]
uses: actions/setup-node@v2
with:
node-version: 16.x
cache: "npm"
- name: Npm Install && Build with Testing preset
run: npm install && npm run-script build
- name: Install Lua
uses: leafo/gh-actions-lua@v8
with:
luaVersion: "luajit-2.1.0-beta3"
- name: Install LuaRocks
uses: leafo/[email protected]
- name: Install Busted
run: luarocks install busted
- name: Running Tests
run: cd dist && busted
```

Without going in to much detail, this workflow triggers whenever you push a commit to your github repository, installs Node.js, Lua, Luarocks and Busted installs all of your project dependencies and compiles a fresh version of the project, at the very end it moves in to the dist folder and runs your busted tests exactly like you would. Now, if you commit and push these changes to your GitHub repository you should be able to see, Under the Actions tab at the top, something like this:

![Github action result](/images/github-workflow-result.png)

Now that’s just it, this job will run every time you push a new commit to the repository, if it fails it’ll show up as a red cross, and you can find which test failed in the logs.

## TSTL Library specifics

This section applies to projects described in [Publishing Modules](publishing-modules.md), as you might know, library projects don’t (and shouldn’t) include the luaLibBundle and also any dependencies it would need to run, so how do we make it run standalone, for testing, we’re going to utilize the fact that you can pass in a different `tsconfig.json` to the compiler, so, the regular one is implicitly used based on the name `tsconfig.json`, we’re going to create a new one like `tsconfig.test.json` you can pretty much duplicate it from your current one, we’re just going to change a few values:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you suddenly mentioning luaLibBundle here? this is the first time that's referenced in this article, and I don't see how it is relevant - shouldn't be needed for busted right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a weird setup, why not have a tsconfig.json inside your /spec/ directory? It should also be possible to just give that its own dist (or just do no outdir and just have lua next to TS files in that directory).


```json title=tsconfig.test.json
{
"compilerOptions": {
"lib": ["esnext"],
"rootDir": "src",
"outDir": "dist" => "testing",
"target": "esnext",
"moduleResolution": "node",
"types": [
"typescript-to-lua/language-extensions",
"lua-types/jit",
"busted-tstl"
],
"declaration": true,
"noImplicitAny": true,
},
"tstl": {
"luaLibImport": "none" => "require",
"buildMode": "library" => "default",
"luaTarget": "JIT",
"noImplicitSelf": true,
"sourceMapTraceback": false
}
}
```

_(arrows are to indicate the changes, don't include them in the actual code)_

Now you can use this new tsconfig like: `tstl —-project tsconfig.test.json`

It will output the new build in to the testing folder and you can move your terminal in to the folder and run busted

Then you can edit your `package.json` file and add a new task:

```json title=package.json
Copy link
Member

@Perryvw Perryvw Sep 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using diff instead of json highlighting might be better for this one:

  "scripts": {
    "build": "tstl",
    "dev": "tstl --watch",
+   "pretest": "tstl --project spec/tsconfig.json",
+   "test": "busted spec"
  },

"scripts": {
"build": "tstl",
+ "test-build": "tstl --project tsconfig.test.json",
"dev": "tstl --watch"
},
```

Next we’ll edit our GitHub actions workflow file to work with this new command like so:

```yaml title=testing.yml
name: TSTL Testing

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build_tests:
name: Busted Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Use Node.js [16.x]
uses: actions/setup-node@v2
with:
node-version: 16.x
cache: "npm"
- name: Npm Install && Build with Testing preset
run: npm install && npm run-script test-build
- name: Install Lua
uses: leafo/gh-actions-lua@v8
with:
luaVersion: "luajit-2.1.0-beta3"
- name: Install LuaRocks
uses: leafo/[email protected]
- name: Install Busted
run: luarocks install busted
- name: Running Tests
run: cd testing && busted
```

Now your build should include all the Lua libraries you installed with npm/yarn, include the lualib bundle file and the GitHub action should work.

## An Example Repository

If some things are still unclear you can always check a repository that followed this guide: https://github.com/qeffects/tstl-testing
1 change: 1 addition & 0 deletions sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"advanced/writing-declarations",
"advanced/compiler-annotations",
"advanced/language-extensions",
"advanced/automated-testing",
"jsx",
{
"type": "category",
Expand Down
Binary file added static/images/busted-output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/images/github-workflow-result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/images/testing-project-setup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.