A NodeMCU Lua API mocks used for unit testing.
I use this library to unit test my NodeMCU lua projects. And lately I started using it for integration testing as well. For example see NodeMCU Device project.
Implementation now is sufficient enough to actually simulate NodeMCU functionality.
For example tmr
is fully emulated, including quirks like tmr.now()
31-bit cycle-over. Wifi
is also simulated enough to fake AP connection/disconnection events. And net.TCP
server too, including thinks like data chunking for TCP frame sizes. Even node
input and output are simulated.
Basically all interfaces are implemented ... Just kidding ... My suggestion is to check the code at src/main/lua for clear view what is working and how. For rest, contributions are much appreciated.
Source code is annotated using LuaLS, most of the data structures are abstracted as classes. One can refer to these in production code in oder to benefit from code assistance.
adc, bit, crypto, dht, encoder, enduser_setup, file, gpio, i2c, mdns, net, node, ow, pwm, rotary, rtcmem, rtctime, sjson, sntp, tmr, u8g2, u8g, wifi
Stereotypical setup of a test file looks like:
local lu = require('luaunit')
local nodemcu = require('nodemcu')
function testTrigger()
nodemcu.reset()
local calls = ""
gpio.trig(1,"both",function(level, time)
calls = calls .. tostring(level)
end)
nodemcu.gpio_set(1, gpio.LOW)
nodemcu.gpio_set(1, gpio.HIGH)
lu.assertEquals(calls, "01")
end
os.exit(lu.run())
Or another example with tmr
:
local lu = require('luaunit')
local nodemcu = require('nodemcu')
function testDynamicTimer()
nodemcu.reset()
local fncCalled = 0
local t = tmr.create()
t:register( 1, tmr.ALARM_SINGLE, function(timerObj)
fncCalled = 1
timerObj:unregister()
end)
lu.assertTrue( t:start() )
nodemcu.advanceTime(100)
lu.assertEquals(fncCalled,1)
end
os.exit(lu.run())
For complete set of ideas how to unit test with NodeMCU API, check existing tests in folder tests/
. They give fairly good idea what can be done and how. Another source of ready examples is NodeMCU Device project.
Add lua/?.lua
directory to your LUA_PATH
.
In order to simplify the usage, all external dependencies are included in this repo as-is.
Instructions in short, in order to unit/integration test using the library one has to:
- use
require("<module>")
in source and test code explicitly, even for built-in modules likefile
,node
and etc. - import
nodemcu
module in the test file, which emulates NodeMCU device itself. - call
nodemcu.reset()
before each individual test. - every time, where expecting some async operation to perform, like a timer to fire, io to send/receive data, node task to trigger, wifi callback to fire and similar, one has to advance the time inside the test manually by using
nodemcu.advanceTime(<ms>)
. This simulates time advance inside emulated NodeMCU device. - all external input, from likes of
gpio
,net
,wifi
and similar, use respectivenodemcu
methods to simulate external events for those activities.
Everything else, more or less should be as-is when coding against the real NodeMCU devices.
Import NodeMCU and make sure before each individual test to reset its state:
local nodemcu = require("nodemcu")
...
-- before each test case
nodemcu.reset()
...
When external input is required, like wifi
, net
-connections and data, gpio
and adc
read data, use nodemcu
methods to do that. For example:
local nodemcu = require("nodemcu")
...
-- assign callback to provide input to adc.read
nodemcu.adc_read_cb = function() return 55 end
...
-- simulate high-signal arrives to pin 1
gpio.mode(1, gpio.INPUT)
nodemcu.gpio_set(1,gpio.HIGH)
-- or use sequence for single values
nodemcu.gpio_set(1,someSequenceFunc)
...
-- capture writes to some gpio pin
gpio.mode(1, gpio.OUTPUT)
nodemcu.gpio_capture(1,function(pin,val) assert(pin==1) assert(val==gpio.HIGH) end)
gpio.write(1, gpio.HIGH) -- capture function will trigger and assert the value
...
All emulation code is located in nodemcu-module
, inspect that to understand what kind of support methods there are available.
Main idea is that all external interactions are emulated via dedicated APIs.
NodeMCU API is exclusively designed on asynchronous, event driven processing using callbacks.
This requires some sort of time management, where time before and after an event is properly tracked. And because there is no native thread model in Lua, I've opted of modeling time exclusively. In other words:
- there is an internal
Timer
object which keeps track of scheduled activities - time of this
Timer
advances manually i.e. one has to callnodemcu.advanceTime(ms)
So, basic use pattern is following :
- create timer like
tmr.create(2,...):start()
which created a timer event after 2ms. - call
nodemcu.advanceTime(1)
and the timer will advance with 1ms i.e. no timer event will be fired yet. - call
nodemcu.advanceTime(2)
and the timer event will be fired.
tmr
module is supported, static and dynamic timers included.
tmr.now()
is simulated, including 31-bit rollover.
Wifi is mostly simulated and rest dummied.
nodemcu-modules
exposes some data structures to capture wifi's state.
Event dispatching is also supported.
In past I had some methods to simulate wifi connect-disconnect sequence but in later updates I've removed them, as these were somewhat hard to work with. They can be brought back but we should find some simplifications first.
Presently TCP server is implemented. I gather UDP can be done too but I haven't had the need yet.
Faking client connection from outside is done via nodemcu.net_tpc_connect_to_listener
which returns a socket
connected to some listener.
Sending data to the server connection happens via socket.sentByRemote
. And accepting data from server happens via socket.receivedByRemote
.
One can check the source code of net-tcp-socket.lua
for methods documented to be used by unit tests to grasp the full picture.
gpio
triggers are supported, or I hope so.
All gpio.write(pin,val)
can be captured to user function via nodemcu.gpio_capture(pin,function(pin,val)void)
.
All external input to pins can be simulated via nodemcu.gpio_set(pin,val)
or nodemcu.gpio_set(pin,cb)
. This method also triggers gpio pin triggers.
Best would be to inspect the source code of respective module to get the idea of what is mocked and how it behaves.
This is dummy at the moment and I have zero ideas how to simulate it. But I'd love to have someting meaningfull, any suggestion is most welcome.
git clone https://github.com/fikin/nodemcu-lua-mocks.git
make test
export LUA_PATH=$(pwd)/lua/?.lua
GPLv3, see LICENSE file Contributions :
- src/contrib/lua/JSON.lua, see its header.
- src/contrib/lua/md5.lua, see its header.
- src/contrib/lua/sha2.lua, see its header.
- luaunit, see library's own details