class Win32HooksWithMocksTests(unittest.TestCase): """Unit tests for the Win32Hook class with mocks for low level dependencies""" def setUp(self): """Set some data and ensure the application is in the state we want""" self.hook = Hook() # Save pointers to original system calls before mocking self.CallNextHookEx = windll.user32.CallNextHookEx self.PeekMessageW = windll.user32.PeekMessageW self.TranslateMessage = windll.user32.TranslateMessage self.DispatchMessageW = windll.user32.DispatchMessageW self.atexit_register = atexit.register self.sys_exit = sys.exit self.fake_kbhook_id = 22 self.fake_mousehook_id = 33 def tearDown(self): """Cleanups after finishing a test""" self.hook.stop() # Restore the pointers to original system calls after mocking windll.user32.CallNextHookEx = self.CallNextHookEx windll.user32.PeekMessageW = self.PeekMessageW windll.user32.TranslateMessage = self.TranslateMessage windll.user32.DispatchMessageW = self.DispatchMessageW atexit.register = self.atexit_register sys.exit = self.sys_exit def test_none_hook_handler(self): """Test running a hook without a handler The next hook in chain still should be called Simulate an odd situation when we got a hook ID (a hook is inserted) but a handler for the hook processing wasn't supplied by a user """ self.hook.keyboard_id = self.fake_kbhook_id self.hook.mouse_id = self.fake_mousehook_id self.hook.handler = None # replace the system API with a mock object windll.user32.CallNextHookEx = mock.Mock(return_value=0) # prepare arguments for _keyboard_ll_hdl call kbd = win32structures.KBDLLHOOKSTRUCT(0, 0, 0, 0, 0) res = self.hook._keyboard_ll_hdl(-1, 3, id(kbd)) windll.user32.CallNextHookEx.assert_called_with( self.fake_kbhook_id, -1, 3, id(kbd)) self.assertEqual(res, 0) # Setup a fresh mock object and arguments for _mouse_ll_hdl call windll.user32.CallNextHookEx = mock.Mock(return_value=0) mouse = win32structures.MSLLHOOKSTRUCT((11, 12), 0, 0, 0, 0) res = self.hook._mouse_ll_hdl(-1, 3, id(mouse)) self.assertEqual(res, 0) windll.user32.CallNextHookEx.assert_called_with( self.fake_mousehook_id, -1, 3, id(mouse)) def test_keyboard_hook_exception(self): """Test handling an exception in a keyboard hook""" self.hook.handler = _on_hook_event_with_exception windll.user32.CallNextHookEx = mock.Mock(return_value=0) kbd = win32structures.KBDLLHOOKSTRUCT(0, 0, 0, 0, 0) self.hook.keyboard_id = self.fake_kbhook_id # Verify CallNextHookEx is called even if there is an exception is raised self.assertRaises(ValueError, self.hook._keyboard_ll_hdl, -1, 3, id(kbd)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with( self.fake_kbhook_id, -1, 3, id(kbd)) self.assertRaises(ValueError, self.hook._keyboard_ll_hdl, 0, 3, id(kbd)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with( self.fake_kbhook_id, 0, 3, id(kbd)) def test_mouse_hook_exception(self): """Test handling an exception in a mouse hook""" self.hook.handler = _on_hook_event_with_exception windll.user32.CallNextHookEx = mock.Mock(return_value=0) mouse = win32structures.MSLLHOOKSTRUCT((11, 12), 0, 0, 0, 0) self.hook.mouse_id = self.fake_mousehook_id # Verify CallNextHookEx is called even if there is an exception is raised self.assertRaises(ValueError, self.hook._mouse_ll_hdl, -1, 3, id(mouse)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with( self.fake_mousehook_id, -1, 3, id(mouse)) self.assertRaises(ValueError, self.hook._mouse_ll_hdl, 0, 3, id(mouse)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with( self.fake_mousehook_id, 0, 3, id(mouse)) @mock.patch.object(Hook, '_process_win_msgs') @mock.patch.object(Hook, 'is_hooked') def test_listen_loop(self, mock_is_hooked, mock_process_msgs): """Test running the main events loop""" atexit.register = mock.Mock(return_value=0) mock_is_hooked.side_effect = [1, 0] # exit at a second loop mock_process_msgs.return_value = 0 # mock hook IDs self.hook.keyboard_id = self.fake_kbhook_id self.hook.mouse_id = self.fake_mousehook_id # run the events loop self.hook.listen() # verify atexit.register calls atexit.register.assert_called() self.assertEqual(len(atexit.register.mock_calls), 2) name, args, kwargs = atexit.register.mock_calls[0] self.assertEqual(args[1], self.fake_kbhook_id) name, args, kwargs = atexit.register.mock_calls[1] self.assertEqual(args[1], self.fake_mousehook_id) # verify is_hooked method calls mock_is_hooked.assert_called() self.assertEqual(len(mock_is_hooked.mock_calls), 2) # verify _process_win_msgs method has been called mock_process_msgs.assert_called() self.assertEqual(len(mock_process_msgs.mock_calls), 1) @mock.patch.object(Hook, 'stop') def test_process_win_msg(self, mock_stop): """Test Hook._process_win_msgs""" # Mock external API windll.user32.PeekMessageW = mock.Mock(side_effect=[1, 0]) windll.user32.TranslateMessage = mock.Mock() windll.user32.DispatchMessageW = mock.Mock() # Test processing the normal messages self.hook._process_win_msgs() windll.user32.PeekMessageW.assert_called() windll.user32.TranslateMessage.assert_called() windll.user32.DispatchMessageW.assert_called() # Test processing WM_QUIT def side_effect(*args): """Emulate reception of WM_QUIT""" args[0].contents.message = win32con.WM_QUIT return 1 windll.user32.PeekMessageW = mock.Mock(side_effect=side_effect) self.assertRaises(SystemExit, self.hook._process_win_msgs) mock_stop.assert_called_once() def test_is_hooked(self): """Verify Hook.is_hooked method""" self.assertEqual(self.hook.is_hooked(), False) self.hook.mouse_is_hook = True self.assertEqual(self.hook.is_hooked(), True) self.hook.mouse_is_hook = False self.assertEqual(self.hook.is_hooked(), False) self.hook.keyboard_is_hook = True self.assertEqual(self.hook.is_hooked(), True) self.hook.keyboard_is_hook = False self.assertEqual(self.hook.is_hooked(), False) self.hook.hook(mouse=False, keyboard=False) self.assertEqual(self.hook.is_hooked(), False)
class Win32HooksWithMocksTests(unittest.TestCase): """Unit tests for the Win32Hook class with mocks for low level dependencies""" def setUp(self): """Set some data and ensure the application is in the state we want""" self.hook = Hook() # Save pointers to original system calls before mocking self.CallNextHookEx = windll.user32.CallNextHookEx self.PeekMessageW = windll.user32.PeekMessageW self.TranslateMessage = windll.user32.TranslateMessage self.DispatchMessageW = windll.user32.DispatchMessageW self.atexit_register = atexit.register self.sys_exit = sys.exit self.fake_kbhook_id = 22 self.fake_mousehook_id = 33 def tearDown(self): """Cleanups after finishing a test""" self.hook.stop() # Restore the pointers to original system calls after mocking windll.user32.CallNextHookEx = self.CallNextHookEx windll.user32.PeekMessageW = self.PeekMessageW windll.user32.TranslateMessage = self.TranslateMessage windll.user32.DispatchMessageW = self.DispatchMessageW atexit.register = self.atexit_register sys.exit = self.sys_exit def test_none_hook_handler(self): """Test running a hook without a handler The next hook in chain still should be called Simulate an odd situation when we got a hook ID (a hook is inserted) but a handler for the hook processing wasn't supplied by a user """ self.hook.keyboard_id = self.fake_kbhook_id self.hook.mouse_id = self.fake_mousehook_id self.hook.handler = None # replace the system API with a mock object windll.user32.CallNextHookEx = mock.Mock(return_value=0) # prepare arguments for _keyboard_ll_hdl call kbd = win32structures.KBDLLHOOKSTRUCT(0, 0, 0, 0, 0) res = self.hook._keyboard_ll_hdl(-1, 3, id(kbd)) windll.user32.CallNextHookEx.assert_called_with(self.fake_kbhook_id, -1, 3, id(kbd)) self.assertEqual(res, 0) # Setup a fresh mock object and arguments for _mouse_ll_hdl call windll.user32.CallNextHookEx = mock.Mock(return_value=0) mouse = win32structures.MSLLHOOKSTRUCT((11, 12), 0, 0, 0, 0) res = self.hook._mouse_ll_hdl(-1, 3, id(mouse)) self.assertEqual(res, 0) windll.user32.CallNextHookEx.assert_called_with(self.fake_mousehook_id, -1, 3, id(mouse)) def test_keyboard_hook_exception(self): """Test handling an exception in a keyboard hook""" self.hook.handler = _on_hook_event_with_exception windll.user32.CallNextHookEx = mock.Mock(return_value=0) kbd = win32structures.KBDLLHOOKSTRUCT(0, 0, 0, 0, 0) self.hook.keyboard_id = self.fake_kbhook_id # Verify CallNextHookEx is called even if there is an exception is raised self.assertRaises(ValueError, self.hook._keyboard_ll_hdl, -1, 3, id(kbd)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with(self.fake_kbhook_id, -1, 3, id(kbd)) self.assertRaises(ValueError, self.hook._keyboard_ll_hdl, 0, 3, id(kbd)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with(self.fake_kbhook_id, 0, 3, id(kbd)) def test_mouse_hook_exception(self): """Test handling an exception in a mouse hook""" self.hook.handler = _on_hook_event_with_exception windll.user32.CallNextHookEx = mock.Mock(return_value=0) mouse = win32structures.MSLLHOOKSTRUCT((11, 12), 0, 0, 0, 0) self.hook.mouse_id = self.fake_mousehook_id # Verify CallNextHookEx is called even if there is an exception is raised self.assertRaises(ValueError, self.hook._mouse_ll_hdl, -1, 3, id(mouse)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with(self.fake_mousehook_id, -1, 3, id(mouse)) self.assertRaises(ValueError, self.hook._mouse_ll_hdl, 0, 3, id(mouse)) windll.user32.CallNextHookEx.assert_called() windll.user32.CallNextHookEx.assert_called_with(self.fake_mousehook_id, 0, 3, id(mouse)) @mock.patch.object(Hook, '_process_win_msgs') @mock.patch.object(Hook, 'is_hooked') def test_listen_loop(self, mock_is_hooked, mock_process_msgs): """Test running the main events loop""" atexit.register = mock.Mock(return_value=0) mock_is_hooked.side_effect = [1, 0] # exit at a second loop mock_process_msgs.return_value = 0 # mock hook IDs self.hook.keyboard_id = self.fake_kbhook_id self.hook.mouse_id = self.fake_mousehook_id # run the events loop self.hook.listen() # verify atexit.register calls atexit.register.assert_called() self.assertEqual(len(atexit.register.mock_calls), 2) name, args, kwargs = atexit.register.mock_calls[0] self.assertEqual(args[1], self.fake_kbhook_id) name, args, kwargs = atexit.register.mock_calls[1] self.assertEqual(args[1], self.fake_mousehook_id) # verify is_hooked method calls mock_is_hooked.assert_called() self.assertEqual(len(mock_is_hooked.mock_calls), 2) # verify _process_win_msgs method has been called mock_process_msgs.assert_called() self.assertEqual(len(mock_process_msgs.mock_calls), 1) @mock.patch.object(Hook, 'stop') def test_process_win_msg(self, mock_stop): """Test Hook._process_win_msgs""" # Mock external API windll.user32.PeekMessageW = mock.Mock(side_effect=[1, 0]) windll.user32.TranslateMessage = mock.Mock() windll.user32.DispatchMessageW = mock.Mock() # Test processing the normal messages self.hook._process_win_msgs() windll.user32.PeekMessageW.assert_called() windll.user32.TranslateMessage.assert_called() windll.user32.DispatchMessageW.assert_called() # Test processing WM_QUIT def side_effect(*args): """Emulate reception of WM_QUIT""" args[0].contents.message = win32con.WM_QUIT return 1 windll.user32.PeekMessageW = mock.Mock(side_effect=side_effect) self.assertRaises(SystemExit, self.hook._process_win_msgs) mock_stop.assert_called_once() def test_is_hooked(self): """Verify Hook.is_hooked method""" self.assertEqual(self.hook.is_hooked(), False) self.hook.mouse_is_hook = True self.assertEqual(self.hook.is_hooked(), True) self.hook.mouse_is_hook = False self.assertEqual(self.hook.is_hooked(), False) self.hook.keyboard_is_hook = True self.assertEqual(self.hook.is_hooked(), True) self.hook.keyboard_is_hook = False self.assertEqual(self.hook.is_hooked(), False) self.hook.hook(mouse=False, keyboard=False) self.assertEqual(self.hook.is_hooked(), False)