Questions about mocking components

jdbaptista
Posts: 6
Joined: Wed Sep 25, 2024 5:13 pm

Questions about mocking components

Postby jdbaptista » Mon Mar 17, 2025 7:27 pm

Hello,

I am attempting to write unit tests that verify a mutex is taken within particular functions, which I have created a component for called app_errors. An example of one such function is:

Code: Select all

/**
 * @brief Moves the error state machine from the NO_ERR state to the
 *        NO_CONNECT state.
 * 
 * @note If a no connect error was already thrown, nothing happens.
 * @note If the state machine is in the HANDLEABLE_ERR state, then this function
 *       moves the FSM to the HANDLEABLE_AND_NO_SERVER_CONNECT_ERR state.
 * 
 * @param[in] errRes Global error state and task error synchronization 
 *        primitives.
 * @param[in] callerHasErrMutex Whether the caller already has possession of
 *        the error synchronization mutex, in which case the function will not
 *        attempt to take it.
 */
void throwNoConnError(ErrorResources *errRes, bool callerHasErrMutex) {
  if (!callerHasErrMutex) {
    while (xSemaphoreTake(errRes->errMutex, INT_MAX) != pdTRUE) {}
  }
  
  switch (errRes->err) {
    case NO_ERR:
      errRes->err = NO_SERVER_CONNECT_ERR;
      /* falls through */
    case NO_SERVER_CONNECT_ERR:
      if (errRes->errTimer == NULL) {
        startErrorFlashing(errRes, true);
      }
      break;
    case HANDLEABLE_ERR:
      errRes->err = HANDLEABLE_AND_NO_SERVER_CONNECT_ERR;
      // do not flash because solid takes priority
      break;
    case HANDLEABLE_AND_NO_SERVER_CONNECT_ERR:
      break;
    case FATAL_ERR:
      throwFatalError(errRes, true);
      break;
    default:
      throwFatalError(errRes, true);
      break;
  }

  if (!callerHasErrMutex) {
    xSemaphoreGive(errRes->errMutex);
  }
}
In order to test this, I can either create a complex system of macros, or mock the xSemaphoreTake and xSemaphoreGive functions using CMock or a similar system. I have done some research on mocking in ESP-IDF and it seems that mocking is done component wide (and typically only for linux), so in this case I would mock the entire freertos component (instead of just freertos/semphr.c).

I have two goals:

1. I would like the mock component to only be used in the app_errors test component instead of replacing the freertos component globally. This will allow me to run unit tests for other components (some of which depend on actual freertos functionality) in the same Unity application as some of the other tests. From the documentation, mocking is currently implemented by replacing entire components with components of the same name, which I believe means that the freertos mock would replace the component for every test component and, crucially, the Unity component.

2. I would like to create a freertos mock component that can be built for both an esp32 and esp32-s3. I'm not well versed in esp-idf mocking, so I'm not sure if I can set up a Linux VM on my Windows desktop and make use of the built-in freertos mock (which only builds for Linux). Even if I can use that mock for this use case, I would still like to avoid the headache of setting that up if possible.

I have a limited understanding of the overall esp-idf build system, but I am under the assumption that the vision I have described is not possible currently because components are global. That said, would it be difficult to add a function to the build system that explicitly replaces component dependencies with other components? Given that my component under test only imports "freertos/FreeRTOS.h" and "freertos/semphr.h", I would be able to create a new mock component that contains these two header files with custom xSemaphoreTake and xSemaphoreGive functions, plus typical mocking functions to set expectations (ie. use CMock to mock the file). The test component CMakeList.txt file would then be able to force the component under test to require the mock component instead of the regular one. I'm not sure if this would break things due to multiple "freertos/semphr.h" header files existing in the codebase, but if that is not the case this would greatly help me here. I would still need to separate the Unity build for app_errors from the Unity build for testing other components that require the real functionality of app_errors, however it would at least not require me to destroy the Unity component's freertos dependency.

Is there currently a way to mock the component such that I achieve the goals I have listed? Alternatively, is the feature I described feasible or is there a simpler way of writing a test for these mutexes?

jdbaptista
Posts: 6
Joined: Wed Sep 25, 2024 5:13 pm

Re: Questions about mocking components

Postby jdbaptista » Tue Apr 22, 2025 2:50 pm

Update:

I still haven't figured out how to mock esp-idf components, however I have figured out how to mock my own components in a straightforward manner by setting environment variables for CMake to use. I use the VSCode extension's multiple configuration feature, which is helpful for creating different builds with different components mocked. My process is below:

1. Each project configuration option defines the environment variable.

Code: Select all

"env": {
  "executable": "test_ota"
},
2. The TEST_COMPONENTS CMake variable is set depending on the value of "$ENV{executable}".

Code: Select all

message(STATUS "executable: $ENV{executable}")
if ("$ENV{executable}" STREQUAL "test_ota")
    set(TEST_COMPONENTS "ota")
endif()
3. The main component conditionally compiles the correct main source file based on the variable.

Code: Select all

if ("$ENV{executable}" STREQUAL "main")
    set(srcs "src/main.c"
             "src/initialize.c")
    set(includes "include")
elseif ("$ENV{executable}" STREQUAL "test_ota")
    set(srcs "src/test_ota_main.c"
             "src/initialize.c")
    set(includes "include")
else()
    message(FATAL_ERROR "Value of executable environment variable $ENV{executable} is not known!")
endif()

set(requires ...)

idf_component_register(SRCS ${srcs}
                        INCLUDE_DIRS ${includes}
                        REQUIRES ${requires})
4. The ota testing component contains a file that defines configuration options that control mocking, which is included as a default file in the test_ota configuration.

Code: Select all

# list of mocked components for ota unit tests
CONFIG_MOCK_INDICATORS=y
5. Other components use the configuration option to conditionally compile mocking source files instead of regular sources and to add mocking control header files.

Code: Select all

set(srcs "src/indicators.c")
set(include_dirs "include")
set(requires common app_errors animations)

if (CONFIG_MOCK_INDICATORS)
    set(srcs "mock/src/mock_indicators.c")
    list(APPEND include_dirs "mock/include")
endif ()

idf_component_register(SRCS ${srcs}
                    INCLUDE_DIRS ${include_dirs}
                    REQUIRES ${requires})

By making each project configuration build path unique, you will not need to rebuild the entire testing app every time.

Who is online

Users browsing this forum: PetalBot, Qwantbot, Semrush [Bot] and 2 guests