Pytest path of mock.patch() for third party package

Question:

Why the 1st case succeed. But the 2nd case failed AssertionError: Expected 'Jenkins' to have been called.

util.py

from jenkinsapi.jenkins import Jenkins
import os

class Util:
    @staticmethod
    def rm(filename):
        os.remove(filename)

    @staticmethod
    def get_jenkins_instance():
        Jenkins(
            'host',
            username='username',
            password='password',
            ssl_verify=False,
            lazy=True)

test_util.py

import pytest
from util import Util

def test_util_remove(mocker):
    m = mocker.patch('os.remove')
    Util.rm('file')
    m.assert_called()

def test_util_get_instance(mocker):
    m = mocker.patch('jenkinsapi.jenkins.Jenkins')
    Util.get_jenkins_instance()
    m.assert_called()

Two files are in the same root folder.

Asked By: mCY

||

Answers:

Not very clear what’s the differences between Python’s import and from ... import ....

But if you use from ... import ..., the mock looks as following:

util.py

from jenkinsapi.jenkins import Jenkins     # <-- difference A
class Util:
    @staticmethod
    def get_jenkins_instance():
        Jenkins(
            'host',
            username='username',
            password='password',
            ssl_verify=False,
            lazy=True)

test_util.py

import pytest
from util import Util
def test_util_get_instance(mocker):
    m = mocker.patch('util.Jenkins')       # <-- difference B
    Util.get_jenkins_instance()
    m.assert_called()

If you use import directly, the mock looks as following:

util.py

import jenkinsapi.jenkins                  # <-- difference A
class Util:
    @staticmethod
    def get_jenkins_instance():
        jenkinsapi.jenkins.Jenkins(
            'host',
            username='username',
            password='password',
            ssl_verify=False,
            lazy=True)

test_util.py

import pytest
from util import Util
def test_util_get_instance(mocker):
    m = mocker.patch('jenkinsapi.jenkins.Jenkins')     # <-- difference B
    Util.get_jenkins_instance()
    m.assert_called()

========== Edit (Aug 5, 2022) ==========

Here’s the reason why patched like this.

a.py
    -> Defines SomeClass

b.py
    -> from a import SomeClass
    -> some_function instantiates SomeClass

Now we want to test some_function but we want to mock out SomeClass using patch(). The problem is that when we import module b, which we will have to do then it imports SomeClass from module a. If we use patch() to mock out a.SomeClass then it will have no effect on our test; module b already has a reference to the real SomeClass and it looks like our patching had no effect.

The key is to patch out SomeClass where it is used (or where it is looked up ). In this case some_function will actually look up SomeClass in module b, where we have imported it.

Answered By: mCY
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.