0

How can I mock a class to test its methods in isolation when they use instance variables? This is an example of the code I am trying to test.

class Employee:
    def __init__(self, id):
        self.id = id
        self.email = self.set_email()

    def set_email():
        df = get_all_info()
        return df[df[id] == self.id].email[0]

def get_all_info():
    # ...

My thought is to Mock the Employee class then call the set_email method to test it. The testing code:

def test_set_email(get_all_info_mock):
    # ...
    mock_object = unittest.mock.Mock(Employee)

    actual_email = Employee.set_email(mock_object)

    assert actual_email == expected_email

When running the test I get the following error.

AttributeError: Mock object has no attribute 'id'

I tried to follow the directions here: Better way to mock class attribute in python unit test. I've also tried to set the mock as a property mock, id witha side_effect, and id with a return_value but I can't seem to figure it out. I don't want to patch the Employee class because the goal is to test it's methods.

Daniel Butler
  • 3,239
  • 2
  • 24
  • 37
  • Try `mock_object = unittest.mock.Mock(Employee, autospec=True)` – rdas Apr 23 '19 at 16:53
  • I get the same error. Without the `autospec=True` when debugging the mock object would look like this `` – Daniel Butler Apr 23 '19 at 17:07
  • Don't mock the class; that's what you are testing. Mock `get_all_info` instead and create a real instance of `Employee`. – chepner Apr 23 '19 at 17:29
  • @chepner I'm testing `set_email`. If I create an instance of the employee class it will call all of the methods in `__init__` which is what I am trying to avoid. – Daniel Butler Apr 23 '19 at 17:32
  • Why? If you mock `get_all_info` to return a static data frame (instead of querying some outside resource, there's no problem with calling `set_email`. – chepner Apr 23 '19 at 17:34
  • @chepner Because then I know that `set_email` is doing what I expect. Lets say `set_email` needs to be changed in a few months how would I know that it is working as expected? It is a simple method by design. What I need is a dummy object with an id attribute in order to test that method. Does that make sense? – Daniel Butler Apr 23 '19 at 17:38
  • If you are worried about `set_email` needing to change, fix that *now*, by having `set_mail` take the *source* of the email as an argument, rather than hiding the data-frame dependency in its body. Right now, you have to know what `set_email` is doing anyway in order to examine the mock that gets returned. – chepner Apr 23 '19 at 17:41
  • I'm not too concerned about mocking the `get_all_info` because it's an easy patch. What you are describing is the disadvantage of using mocks in a unittest. They are incredibly implementation specific. – Daniel Butler Apr 23 '19 at 17:44

1 Answers1

1

All you need to do is set the id attribute.

def test_set_email(get_all_info_mock):
    # ...
    mock_object = unittest.mock.Mock(id=3)  # Or whatever id you need

    actual_email = Employee.set_email(mock_object)

    assert actual_email == expected_email
chepner
  • 497,756
  • 71
  • 530
  • 681
  • That is what I was looking for! I went down the path of creating a separate dummy class but this is way better! Thank you for dealing with me in the comments! – Daniel Butler Apr 23 '19 at 18:04