Unit testing in Nim
What I've learned by writing unit tests in Nim.
Outline
- Initial attempts
- Implementing
expect
- Finding
check
- Testing an async proc
- Conclusion
Initial attempts
When a developer creates a new library project, nimble creates a project scaffolding that includes a basic unit test:
1 | # This is just an example to get you started. You may wish to put all of your |
From this, I can see that the test
word along with a string description is used to separate tests. Running nimble test
runs them and gives an output showing the passing test. Makes sense.
Implementing expect
I expanded on this to write a chunk of tests, and everything pretty much went well. Unfortunately, whenever a test I wrote would fail due to an assertion throwing an exception, I didn't get information on why the assertion was failing, just that it was. I was used to Jest's fantastic output, telling me what the parts of the assertion where at the time of evaluation.
1 | /home/arch/Downloads/tests/test1.nim(13) test1 |
I went and wrote a library to improve on the assertions, so I'd know what each side where, in order to help me debug and fix tests (or the code). I didn't want to have to do this, but I couldn't get information from the assertion, and this was definitely better than echod
'ing every variable before asserting.
1 | /home/arch/Downloads/tests/test1.nim(15) test1 |
Finding check
I was diving through the (rather hard to read) unittest
module, and found check
:
1 | /home/arch/Downloads/tests/test1.nim(14, 18): Check failed: add(5, 5) == expected |
Fantastic!
As a result, I've archived my expect library with a note to use check
instead. Problem solved.
Testing an async proc
I wanted to drop this bit in, as I couldn't really find a clear approach. It turns out that writing a test for an async proc is no different than calling that proc in a normal context - either use an async
proc, or something like waitFor
:
1 | import unittest, asyncdispatch |
I ended up spending hours trying to figure out how to make this pattern happen, as I was getting very obscure error messages during runtime about "no handles or timers registered". Turns out this was an issue with me not setting up a stream correctly. For reference, here's that information.
If your code does this:
1 | import |
then your test needs to look like this:
1 | import |
I was missing the complete
call, which apparently (?) caused the future to not be registered on the global dispatcher.
Conclusion
Writing unit tests in Nim is very simple to get started, thanks to the syntax of the templates in unittest
and the scaffolding that's created for the developer by nimble
when creating a new library project.
The process gets more difficult, however, when the developer needs to move into things like actually seeing the values in an assertion and testing asynchronous procedures. Simple lesson: use check
, not assert
or doAssert
. And, as long as you make sure that your procedures are correctly written, you won't have to muck through a ton of obscure error messages that don't do anything to help you through debugging. Tests are used to make sure that code is written correctly; unfortunately in this instance, I had to make sure my code was working correctly before I could run a test.