feat(test): mocking framework #60
					 3 changed files with 147 additions and 1 deletions
				
			
		|  | @ -4,4 +4,4 @@ add_library_module(fuzz_test test.cpp fuzz.cpp) | |||
| target_link_libraries(test PUBLIC tbb logger) | ||||
| target_link_libraries(fuzz_test PUBLIC tbb logger) | ||||
| 
 | ||||
| add_test_module(test test.test.cpp) | ||||
| add_test_module(test test.test.cpp mock.test.cpp) | ||||
|  |  | |||
							
								
								
									
										52
									
								
								modules/test/private/mock.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								modules/test/private/mock.test.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| #include <test/mock.hpp> | ||||
| #include <test/test.hpp> | ||||
| 
 | ||||
| using namespace lt::test; | ||||
| using namespace lt::test::mock; | ||||
| 
 | ||||
| class ExpensiveClass | ||||
| { | ||||
| private: | ||||
| public: | ||||
| 	virtual int expensive(std::string str, std::optional<int> opt) | ||||
| 	{ | ||||
| 		return 0; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| class MockClass: public ExpensiveClass | ||||
| { | ||||
| public: | ||||
| 	int expensive(std::string str, std::optional<int> opt) override | ||||
| 	{ | ||||
| 		return expensive_mock(str, opt); | ||||
| 	}; | ||||
| 
 | ||||
| 	Mock<int(std::string, std::optional<int>)> expensive_mock {}; | ||||
| }; | ||||
| 
 | ||||
| class ExpensiveUser | ||||
| { | ||||
| public: | ||||
| 	ExpensiveUser(ExpensiveClass &dependency) | ||||
| 	{ | ||||
| 		dependency.expensive("", 10); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // problem #1: matcher functions should construct an invokable object to test against the indexed
 | ||||
| // argument.
 | ||||
| 
 | ||||
| Suite raii = "mock_raii"_suite = [] { | ||||
| 	Case { "happy path won't throw" } = [] { | ||||
| 		auto a = std::function<int(int)> {}; | ||||
| 		auto expensive = MockClass {}; | ||||
| 		auto side_effect = false; | ||||
| 		expensive.expensive_mock.expect("test", std::nullopt) | ||||
| 		    .apply([&](auto str, auto opt) { side_effect = true; }) | ||||
| 		    .returns(69); | ||||
| 
 | ||||
| 		auto user = ExpensiveUser { expensive }; | ||||
| 	}; | ||||
| }; | ||||
							
								
								
									
										94
									
								
								modules/test/public/mock.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								modules/test/public/mock.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | |||
| #pragma once | ||||
| 
 | ||||
| namespace lt::test { | ||||
| 
 | ||||
| template<typename _Signature> | ||||
| class Mock; | ||||
| 
 | ||||
| template<typename Return_Type, typename... Arg_Types> | ||||
| class Mock<Return_Type(Arg_Types...)> | ||||
| { | ||||
| public: | ||||
| 	auto at_least() -> Mock & | ||||
| 	{ | ||||
| 		return *this; | ||||
| 	} | ||||
| 
 | ||||
| 	auto operator&&(Mock &mock) -> Mock & | ||||
| 	{ | ||||
| 		return mock; | ||||
| 	} | ||||
| 
 | ||||
| 	auto operator()(Arg_Types... arguments) -> Return_Type | ||||
| 	{ | ||||
| 		++m_call_index; | ||||
| 
 | ||||
| 		for (auto &side_effect : m_side_effects) | ||||
| 		{ | ||||
| 			side_effect(std::forward<Arg_Types>(arguments)...); | ||||
| 		} | ||||
| 
 | ||||
| 		if (m_return_func) | ||||
| 		{ | ||||
| 			return m_return_func(std::forward<Arg_Types>(arguments)...); | ||||
| 		} | ||||
| 		return m_return_value; | ||||
| 	} | ||||
| 
 | ||||
| 	/** With any arguments. */ | ||||
| 	template<uint32_t counter = 1> | ||||
| 	auto expect() -> Mock & | ||||
| 	{ | ||||
| 		m_expected_counter = counter; | ||||
| 		return *this; | ||||
| 	} | ||||
| 
 | ||||
| 	auto apply(std::function<void(Arg_Types...)> side_effect) -> Mock & | ||||
| 	{ | ||||
| 		m_side_effects.emplace_back(std::move(side_effect)); | ||||
| 		return *this; | ||||
| 	} | ||||
| 
 | ||||
| 	/** Returns a fixed value. */ | ||||
| 	auto returns(Return_Type value) -> Mock & | ||||
| 	{ | ||||
| 		m_return_value = value; | ||||
| 		return *this; | ||||
| 	} | ||||
| 
 | ||||
| 	/** Returns a value based on input. */ | ||||
| 	auto returns(std::function<Return_Type(Arg_Types...)> func) -> Mock & | ||||
| 	{ | ||||
| 		m_return_func = std::move(func); | ||||
| 		return *this; | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	Return_Type m_return_value {}; | ||||
| 
 | ||||
| 	std::function<Return_Type(Arg_Types...)> m_return_func {}; | ||||
| 
 | ||||
| 	std::vector<std::function<void(Arg_Types...)>> m_side_effects {}; | ||||
| 
 | ||||
| 	uint32_t m_call_index = 0; | ||||
| 
 | ||||
| 	std::vector<std::pair<std::tuple<Arg_Types...>, uint32_t>> m_expected_args; | ||||
| 
 | ||||
| 	uint32_t m_expected_counter {}; | ||||
| }; | ||||
| 
 | ||||
| namespace mock::range { | ||||
| 
 | ||||
| [[nodiscard]] auto is_empty() -> bool | ||||
| { | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| }; // namespace mock::range
 | ||||
| 
 | ||||
| [[nodiscard]] auto eq(auto rhs) -> bool | ||||
| { | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| } // namespace lt::test
 | ||||
		Loading…
	
	Add table
		
		Reference in a new issue