commit ff1b18d8089e36cf76f78224f0e899f5e06db7e4 Author: TheoryOfNekomata Date: Tue Feb 14 12:50:15 2023 +0800 Initial commit Add files. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6c0cc39 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# This makes the repo play nice with git on Windows +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a0f976 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +cmake-build-debug +*.o +CMakeFiles/ +CMakeCache.txt +Makefile +cmake_install.cmake +example +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..17ceddd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.24) +project(bdd_for_c_mocks) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c99 -W -Wall") + +include_directories(.) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..afe42f2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Allan Crisostomo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..cc334bf --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1 @@ +Allan Crisostomo diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e5e6d7 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# bdd-for-c-mocks + +## Usage + +Suppose you have a library `lib` and an application `app` that uses `lib`. You want to test `app` and you want to mock +`lib`'s functions. You can do this by creating a mock header file for `lib` and including it in `app`'s test file. + +### `lib.h` + +This is a reference header file for `lib`. The implementation should be abstracted from us, and we want to be able to +mock its behavior. + +```c +#ifndef LIB_H +#define LIB_H + +int add(int a, int b); + +void alert(); + +#endif +``` + +### `lib.mock.h` + +We define mocked behavior for `lib`. `bdd-for-c-mocks` offers a capability to force mock behavior through modes. Note +that all mode behaviors should be implemented already. + +```c +#ifndef LIB_MOCK_H +#define LIB_MOCK_H + +#include + +mock_modes(add) { + ADD_RETURNS_SUM = 0, + ADD_RETURNS_CONSTANT_VALUE, +}; + +mock(add) int add(int a, int b) { + mock_mode_if(add, ADD_RETURNS_SUM) { + // suppose we want to test normal behavior + mock_return(add) a + b; + } else mock_mode_if(add, ADD_RETURNS_CONSTANT_VALUE) { + // maybe an edge case? + mock_return(add) 5; + } +} + +mock(alert) void alert() { + mock_return(alert); +} + +#endif +``` + +### `app.h` +```c +#ifndef APP_H +#define APP_H + +void math(); + +#endif +``` + +### `app.c` + +This is a reference implementation of `app`. We want to test the invocations done inside the `math()` function. + +```c +#include "lib.h" +#include "app.h" + +void math() { + int result_a = add(1, 2); + int result_b = add(3, 4); + if (result_a == result_b) { + // this is the edge case! + // alert() should be called if the add values are the same + alert(); + } +} +``` + +### `app.test.c` + +This is the test file for `app`. It includes `lib.mock.h` instead of `lib.c`. `lib.h` should still be linked because we +are sharing the original function declarations. + +```c +#include +#include "app.h" + +spec("app") { + describe("math") { + after_each() { + mock_reset(add); + } + + after_each() { + mock_reset(alert); + } + + it("calls add") { + mock_mode(add, ADD_RETURNS_SUM); + mock_set_excepted_calls(math, 2); + + math(); + + check( + mock_get_expected_calls(math) == mock_get_actual_calls(math), + "add was not called." + ); + } + + it("calls alert when calls from add return the same values") { + mock_mode(add, ADD_RETURNS_CONSTANT_VALUE); + + math(); + check(mock_is_called(alert), "alert was not called."); + } + } +} + +``` diff --git a/bdd-for-c-mocks.h b/bdd-for-c-mocks.h new file mode 100644 index 0000000..a7041e4 --- /dev/null +++ b/bdd-for-c-mocks.h @@ -0,0 +1,28 @@ +#ifndef BDD_FOR_C_MOCKS_H +#define BDD_FOR_C_MOCKS_H + +#define mock_call_count_t unsigned char + +#define mock(X) static mock_call_count_t actual_##X = 0; + +#define mock_mode_t unsigned char + +#define mock_modes(X) static mock_mode_t current_mock_mode_##X = 0; enum mock_modes_##X + +#define mock_mode(X, Y) current_mock_mode_##X = Y + +#define mock_mode_if(X, Y) if (current_mock_mode_##X == Y) + +#define mock_return(X) actual_##X += 1; return + +#define mock_reset(X) actual_##X = 0 + +#define mock_get_actual_calls(X) (mock_call_count_t) actual_##X + +#define mock_set_expected_calls(X, Y) static const mock_call_count_t expected_##X = Y + +#define mock_get_expected_calls(X) (mock_call_count_t) expected_##X + +#define mock_is_called(X) mock_get_actual_calls(X) > 0 + +#endif