Does this help you?
import unittest
from unittest.mock import MagicMock, patch
class TestGatttoolInteraction(unittest.TestCase):
"""
Test suite for the gatttool interaction functions.
Mocks pexpect.spawn to avoid actual external process calls.
"""
@patch('pexpect.spawn') # Patch pexpect.spawn globally for all tests in this class
def test_connect_success(self, mock_spawn):
"""
Tests that the connect function correctly spawns gatttool,
sends the connect command, and expects success.
"""
# Configure the mock pexpect.spawn object.
# When mock_spawn() is called, it returns a mock object (mock_gatt).
mock_gatt = MagicMock()
mock_spawn.return_value = mock_gatt
# Simulate gatttool's response to the connect command.
# When mock_gatt.expect is called, we want it to succeed without error.
# We don't need to specify return value for expect if it just needs to not raise.
mock_gatt.expect.return_value = 0 # A common return for success in pexpect
# Call the function under test
connect()
# Assertions to verify correct behavior:
# 1. Verify pexpect.spawn was called with the correct command
mock_spawn.assert_called_once_with("sudo gatttool -I")
# 2. Verify the correct connect command was sent
mock_gatt.sendline.assert_any_call("connect " + MAC)
# 3. Verify 'Connection successful' was expected
mock_gatt.expect.assert_called_once_with("Connection successful")
# 4. Verify the global 'gatt' variable was set to the mock object
self.assertIs(gatt, mock_gatt)
@patch('pexpect.spawn')
def test_connect_failure(self, mock_spawn):
"""
Tests that connect raises an exception if 'Connection successful'
is not found, simulating a connection failure.
"""
mock_gatt = MagicMock()
mock_spawn.return_value = mock_gatt
# Configure expect to raise an exception, simulating failure to find expected text
mock_gatt.expect.side_effect = pexpect.exceptions.TIMEOUT('Timeout waiting for "Connection successful"')
# Assert that the function raises the expected pexpect exception
with self.assertRaises(pexpect.exceptions.TIMEOUT):
connect()
mock_spawn.assert_called_once_with("sudo gatttool -I")
mock_gatt.sendline.assert_called_once_with("connect " + MAC)
mock_gatt.expect.assert_called_once_with("Connection successful")
@patch('pexpect.spawn') # Patch pexpect.spawn for this test
def test_send_success(self, mock_spawn):
"""
Tests that the send function correctly calls connect(),
sends the char-write-req command, and expects success.
"""
mock_gatt_instance = MagicMock()
# Ensure that each call to pexpect.spawn() (e.g., from connect())
# returns the same mock object in this test context.
mock_spawn.return_value = mock_gatt_instance
# Simulate successful responses for both connect() and send() operations
# expect() should not raise an error
mock_gatt_instance.expect.return_value = 0
test_value = "ab"
send(test_value)
# Assertions:
# 1. Verify pexpect.spawn was called twice (once by connect() inside send(), then again by connect() in the second call)
# However, because send() calls connect() which *re-spawns* and overwrites 'gatt',
# we only care about the state after the final connect().
# The important thing is that gatt.sendline and gatt.expect are called correctly on the *final* gatt object.
# Since connect() is called, pexpect.spawn will be called once.
mock_spawn.assert_called_once_with("sudo gatttool -I")
# 2. Verify the connect command was sent by connect()
mock_gatt_instance.sendline.assert_any_call("connect " + MAC)
# 3. Verify 'Connection successful' was expected by connect()
mock_gatt_instance.expect.assert_any_call("Connection successful")
# 4. Verify the characteristic write command was sent
expected_write_command = f"char-write-req 0x000c {test_value}0"
mock_gatt_instance.sendline.assert_any_call(expected_write_command)
# 5. Verify 'Characteristic value was written successfully' was expected
mock_gatt_instance.expect.assert_any_call("Characteristic value was written successfully")
# Ensure gatt.sendline and gatt.expect were called for both operations
# We use call_count to ensure both connect and send operations occurred on the mock
self.assertEqual(mock_gatt_instance.sendline.call_count, 2)
self.assertEqual(mock_gatt_instance.expect.call_count, 2)
@patch('pexpect.spawn')
def test_send_write_failure(self, mock_spawn):
"""
Tests that send raises an exception if 'Characteristic value was written successfully'
is not found, simulating a write failure.
"""
mock_gatt_instance = MagicMock()
mock_spawn.return_value = mock_gatt_instance
# Set up expect to succeed for the 'connect' call
mock_gatt_instance.expect.side_effect = [
0, # Success for "Connection successful"
pexpect.exceptions.TIMEOUT('Timeout waiting for "Characteristic value was written successfully"') # Failure for the write
]
test_value = "1a"
with self.assertRaises(pexpect.exceptions.TIMEOUT):
send(test_value)
mock_spawn.assert_called_once_with("sudo gatttool -I")
mock_gatt_instance.sendline.assert_any_call("connect " + MAC)
mock_gatt_instance.expect.assert_any_call("Connection successful")
mock_gatt_instance.sendline.assert_any_call(f"char-write-req 0x000c {test_value}0")
mock_gatt_instance.expect.assert_any_call("Characteristic value was written successfully")
self.assertEqual(mock_gatt_instance.sendline.call_count, 2)
self.assertEqual(mock_gatt_instance.expect.call_count, 2)
if __name__ == '__main__':
unittest.main(argv=['first-arg-is-ignored'], exit=False) # exit=False prevents sys.exit()