Today, while writing some unit tests, I encountered a challenge. The user story was that, when a Person’s details are updated, the display should be updated to reflect the changes.
I’d implemented this feature using a signal on the person class that will be called whenever any details are updated:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class person{public: // Set the Person's name. void name(const std::string & name) { name_ = name; updated(*this); }; // An signal that will be called when the person's details are updated. boost::signal<void(const person & person)> updated;private: // The person's name. std::string name_;}; |
This is a fairly standard application of an observer pattern that you might find in any MVC application.
But the question is, using the Boost unit test framework, how can I test if my signal has been called?
The mock signal handler
To test the signal handler, we’ll use a functor as a mock signal handler, that sets an internal flag when it gets called. In the functor’s destructor, we’ll do a test on the flag to make sure it got set:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
struct mock_handler{ mock_handler(const person & expected_person) : has_been_called_(false), expected_person_(expected_person) {}; // The signal handler function. void operator()(const person & person) { has_been_called_ = true; BOOST_CHECK_EQUAL(&person == &expected_person_, true); }; // This handler must be called before it goes out of scope. ~mock_handler() { BOOST_CHECK_EQUAL(has_been_called_, true); };private: bool has_been_called_; const person & expected_person_;}; |
The test case
Once we’ve written a mock, the test case is pretty simple. Note that I wrap my handler with a boost::ref, so that it doesn’t get copied.
|
1
2
3
4
5
6
7
8
9
10
11
|
// Test that setting a new name triggers the person.updated signal.BOOST_AUTO_TEST_CASE(setting_name_triggers_update_signal){ person subject; mock_handler handler(subject); subject.updated.connect(boost::ref(handler)); // Change the person's name, triggering the updated signal. subject.name("Richard");} |
This works great. And if we comment out the updated signal call in person::name():
|
1
2
3
|
Running 1 test case... person_test.cpp(49): error in "setting_name_triggers_update_signal": check has_been_called_ == true failed [0 != 1]*** 1 failure detected in test suite "tests" |
..then the test case will fail accordingly.
