1

I use unittest in my testing. I import a class in yhab.main package as...

from yhab.blah import SomeClass

def some_func():
    some_instance = SomeClass()
    return some_instance.method()

yhab.blah.SomeClass is defined as...

class SomeClass:
    def method(self):
        return 'hello'

And then I write a test like this...

@mock.patch('yhab.blah.SomeClass')
def test_mock_of_blah_someclass(mock_some_class):
    assert some_func() != 'hello'

the invocation of method() calls the real instance, not a mock.

But if I do this...

@mock.patch('yhab.main.SomeClass')
def test_mock_of_main_someclass(mock_some_class):
    assert some_func() != 'hello'

the invocation of method() calls the mock, not the real instance and the test passes.

Why is that?

I was thinking that python must make some sort of copy of the class definition when an import happens, but I wrote a test that proves that to not be the case.

The docs say the following, which kind of eludes to this, but it doesn't really say it outright, and IMO doesn't really explain it, especially for a python newb...

Patching a class replaces the class with a MagicMock instance. If the class is instantiated in the code under test then it will be the return_value of the mock that will be used.

Do the docs need to be updated to be clear?

Trenton D. Adams
  • 1,805
  • 2
  • 16
  • 20
  • `some_func()` is defined inside `yhab.main`, and given `SomeClass` was imported into `yhab.main` and `some_func` use that directly, it effectively takes the name `yhab.main.SomeClass`. Thus using `@mock.patch('yhab.main.SomeClass')` will be the code that will affect `yhab.main.some_func` given how the code is written in the question. The quoted documentation has nothing to do with the effect being seen. – metatoaster Sep 20 '22 at 00:24
  • Related [thread](https://stackoverflow.com/questions/12317255/python-mock-library-patching-classes-while-unit-testing) and [thread](https://stackoverflow.com/questions/32273582/python-mock-patch-decorator-behaves-different-for-class-methods-and-individual-f) (this question may in fact be duplicate of those). – metatoaster Sep 20 '22 at 00:25
  • @metatoaster You've stated what is known, not why. yhab.main.Some is a pointer to yhab.blah.Someclass, so in my mind the mocks should have the same behaviour. So I'm looking for "why" it behaves this way. Without the why, these sorts of things are difficult to remember. Plus, it would be useful if the answer came up in google results. I will review those other threads you posted. – Trenton D. Adams Sep 20 '22 at 01:19
  • Nope, there no pointers in python - `yhab.main.SomeClass` is assigned with the value from `yhab.blah.SomeClass` via the import statement. Calling the `mock.patch` on that same name replaces that exact assignment with a mock, the original is not affected. Likewise, replacing `yhab.main.SomeClass` with something else won't affect other references that already took it (in your case, it leaves `yhab.blah.SomeClass` untouched). Effectively you are confused about [this](https://stackoverflow.com/questions/38439658/assignments-just-bind-names-to-objects). – metatoaster Sep 20 '22 at 01:47
  • Never mind that [this exact answer](https://stackoverflow.com/a/16071466/) states the issue clearly - you need to know which name is being used. Another point of note is that `mock.patch` effectively change a global variable for some module, and [global variables are only local to the module](https://stackoverflow.com/questions/38246928/using-global-variable-in-python-among-modules-in-different-packages); if you want `@mock.patch('yhab.blah.SomeClass')` to work in your example, you will need use the reference/assignment in the same way everywhere, i.e. `some_instance = yhab.blah.SomeClass()`. – metatoaster Sep 20 '22 at 01:56
  • @metatoaster, thanks. That documentation quote is exactly what I was looking for. So the yhab.main ends up having a variable pointing to SomeClass, and only that variable gets updated, not SomeClass. I proved this by referencing the class directly, without an import to it, and the mocking then still works. – Trenton D. Adams Sep 20 '22 at 03:02

1 Answers1

1

After you import SomeClass from yhab.blah, it ends up in the yhab.main namespace, not in the yhab.blah namespace.

Try to use @mock.patch('yhab.main.SomeClass') instead of @mock.patch('yhab.blah.SomeClass').