0

I have a class

class X:

  def __init__(self, db):
    self.db = db

  def get_data_from_friend(self):
    return None

  def get_data_from_db(self):
    return self.db.get_my_db_data()

  def get_data(self):
    if data := self.get_data_from_friend():
      return data

    return self.get_data_from_db()

And I'm trying to test the get_data method, to validate that the calls inside of it are executed.

I have a test like this

def test_get_data(self):
  mock = create_autospec(X)
  mock.get_data()
  mock.get_data.assert_called_once() # <-- works
  mock.get_data_from_friend.assert_called_once() # <-- assertionError, not called

what am I missing here?

8
  • 2
    I'd suggest: don't. Don't mock parts of the thing you're supposed to be testing - test doubles are for collaborators (in this case, perhaps the injected db). You should be free to refactor inside that class, as long as the observable behaviour through the public API remains the same, supported by tests not constantly fighting them. Note also that mock.get_data() then mock.get_data.assert_called_once() is testing the test, not the implementation - there's no real code involved.
    – jonrsharpe
    Commented Nov 20, 2023 at 11:23
  • 1
    "I have tests in place for the collaborators already" - yes, the collaborators also need to be tested separately, because using test doubles to replace the real implementation means they're not tested here. You should be able to test this without mocking parts of X, but there's so little context left it's impossible to say how exactly.
    – jonrsharpe
    Commented Nov 20, 2023 at 11:30
  • 1
    So, are you wanting to test the method get_data()?
    – quamrana
    Commented Nov 20, 2023 at 11:43
  • 1
    "I disagree, I don't care about the behaviour / implementation of the collaborators here (hence why they are mocked)" - that's not a disagreement, you shouldn't care about the collaborators, that's why they're mocked. But that's not what you're doing; you've mocked the thing you're supposed to be testing, then are somehow surprised when the test double doesn't do what the implementation does (of course it doesn't, that's the point of a test double - when you call get_data on a mock, it just records details of what it was called with and returns a canned value). Mock db, test X.
    – jonrsharpe
    Commented Nov 20, 2023 at 11:44
  • 1
    Autospec matches the interface, not the implementation: it's for "ensuring that the mock objects in your tests have the same api as the objects they are replacing". There's no real X instance in your test at all.
    – jonrsharpe
    Commented Nov 20, 2023 at 11:52

1 Answer 1

0

Your X class is correctly defined and accepts dependency injection so you can do something like this

from unittest.mock import MagicMock

class X:

  def __init__(self, db):
    self.db = db

  def get_data_from_friend(self):
    return None

  def get_data_from_db(self):
    return self.db.get_my_db_data()

  def get_data(self):
    if data := self.get_data_from_friend():
      return data

    return self.get_data_from_db()

MOCK_DATA = "mock data"
def test_get_data():
    db_mock = MagicMock()
    db_mock.get_my_db_data = MagicMock(return_value=MOCK_DATA)
    assert X(db_mock).get_data() == MOCK_DATA

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.