I've recently tried the pattern of using `patchFile` for mocking in unit tests 
and it worked really well for my use case.

The pattern is in a unit test folder you provide a `config.nims` file using 
`patchFile` to override certain modules. Note this will override these modules 
for all tests in this folder (AFAICT).

This unit testing is for a pure-Nim simple object store called 
`nvs_config_obj.nim` which itself uses a Nim-ified wrapper `nvs.nim` that 
provides Non-volatile Storage (NVS) on Zephyr RTOS. I wanted to test both 
`nvs_config_obj` and `nvs` modules from the C API layer up and decided that was 
the best layer to mock. There are two modules that provide the C FFI for the 
low level C Apis from the Zephyr RTOS (`znvs.nim` and `zflash.nim`).

Here's how it worked out. First the `config.nims` setup to override the `znvs` 
and `zflash` modules:
    
    
    switch("path", "$projectDir/../../src")
    
    patchFile("nephyr", "znvs", "znvs_mock")
    patchFile("nephyr", "zflash", "zflash_mock")
    
    
    Run

Next in `znvs_mock.nim` in the same test folder you can override any api's 
you're testing. In this case some C read/write APIs using a Table with some 
byte arrays was sufficient:
    
    
    # ...
    type
      nvs_fs* = object
        # mock the C struct `nvs_fs` with our custom one
        data*: Table[uint16, array[128, byte]]
    
    proc nvs_write*(fs: ptr nvs_fs; id: uint16; data: pointer; len: cint): cint 
=
      # mock nvs c write function, only up to 128 bytes
      var buf: array[128, byte]
      copyMem(buf[0].addr, len.unsafeAddr, 1)
      copyMem(buf[1].addr, data, len)
      fs.data[id] = buf
    
    proc nvs_read*(fs: ptr nvs_fs; id: uint16; data: pointer; len: cint): cint =
      # mock nvs c read function, only up to 128 bytes
      var buf = fs.data[id]
      copyMem(result.addr, buf[0].addr, 1)
      copyMem(data, buf[1].addr, len)
    
    
    Run

Now my unit tests are testing the main `nvs.nim` which wraps some Zephyr RTOS 
bits, but I just call and use the `nvs.nim` module as normal in the unit tests:
    
    
    include nephyr/extras/nvs_config_obj # do an include to access any private 
procs/vars
    
    suite "nvs basic config object":
      test "basic store":
        var nvs = initNvsMock[NvsConfig]()
        var settings = newConfigSettings(nvs, ExampleConfigs())
        
        settings.values.dac_calib_gain = 1111
        settings.values.dac_calib_offset = 2222
        
        var fld3Val = nvs.read(fld3, float32)
        # etc...
    
    
    Run

There's some modifications to how you import modules in `nvs.nim` to help make 
it easier to "intercept" certain modules. In this case it let's me override the 
C header imports it tries to do otherwise which is also nice.

I found a bit of trickiness with the raw NVS constructor vs the high level Nim 
NVS module. I resolved this by passing in the high-level NVS object as a 
generic type. This allowed me to mock only the lower level `znvs.nim` module 
but unit test the higher level NVS wrapper.
    
    
    proc initNvsMock*[T](): T =
      result = T()
      discard nvs_init(result.fs.addr, "flash_mock")
    
    
    
    Run

You can find the 
[config.nims](https://github.com/EmbeddedNim/nephyr/blob/e666520104b45677be3e55ccebd16d0967a88cb3/tests/unit_tests/config.nims#L4)
 and the [mocked zephyr rtos nvs api's 
here](https://github.com/EmbeddedNim/nephyr/blob/e666520104b45677be3e55ccebd16d0967a88cb3/tests/unit_tests/znvs_mock.nim#L35)
 . The actual [unit 
tests](https://github.com/EmbeddedNim/nephyr/blob/e666520104b45677be3e55ccebd16d0967a88cb3/tests/unit_tests/t_nvs_cfg_obj.nim#L35).
 

Reply via email to