@@ -0,0 +1,2 @@ | |||||
# This makes the repo play nice with git on Windows | |||||
* text=auto |
@@ -0,0 +1,8 @@ | |||||
cmake-build-debug | |||||
*.o | |||||
CMakeFiles/ | |||||
CMakeCache.txt | |||||
Makefile | |||||
cmake_install.cmake | |||||
example | |||||
.idea/ |
@@ -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(.) |
@@ -0,0 +1,21 @@ | |||||
The MIT License (MIT) | |||||
Copyright (c) 2023 Allan Crisostomo <allan.crisostomo@outlook.com> | |||||
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. |
@@ -0,0 +1 @@ | |||||
Allan Crisostomo <allan.crisostomo@outlook.com> |
@@ -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 <bdd-for-c-mocks.h> | |||||
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 <bdd-for-c.h> | |||||
#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."); | |||||
} | |||||
} | |||||
} | |||||
``` |
@@ -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 |