pytest : Cannot mock __init__ of my class

12,834

AFAIK you can have two separate approaches here. One is to mock __init__ method directly with your patch, and it will work as expected (original __init__ would not be called).

@mock.patch('mydb.Db.__init__', return_value=None)

Second, with your current approach, instead of assigning None value to mock_db __init__ method (which would not be called along the way, as object is mock already) assign return_value to mock_db which is called to get an instance of the Db class (mock_db.return_value should point to mock version of your Db instance) or leave it as it is if you're not interested in changing behavior of default mock that you'd get as code would be executed (AFAIR when calling a class to create its instance some Mock object is created automatically).

EDIT: You need to be sure that you mock mydb in proper location. The rule of thumb is to mock things where they are imported and not where they are located. So in other words, if you want to test code that is placed in file my_package.my_module that has import mydb you need to patch my_package.my_module.mydb.Db.

Great resource regarding mocks: ELI5 mocks explained

One of the first things that should stick out is that we’re using the mock.patch method decorator to mock an object located at mymodule.os, and injecting that mock into our test case method. Wouldn’t it make more sense to just mock os itself, rather than the reference to it at mymodule.os?

Well, Python is somewhat of a sneaky snake when it comes to imports and managing modules. At runtime, the mymodule module has its own os which is imported into its own local scope in the module. Thus, if we mock os, we won’t see the effects of the mock in the mymodule module. The mantra to keep repeating is this:

Mock an item where it is used, not where it came from.

Share:
12,834
ghostrider
Author by

ghostrider

Updated on June 18, 2022

Comments

  • ghostrider
    ghostrider almost 2 years

    I am having a custom Db class, which has the basic operations. I am trying to write tests around it - on the __init__ of my class I am connecting to actual db which I want to avoid ( since I am just writing unit tests, no need to connect to the actual db ).

    This is my code :

    @mock.patch( 'mydb.Db' )
    @pytest.mark.parametrize( "input,expected", [
        (
            {
                'key'   : "x" ,
                'value' : 5   ,
            } , 
            [  "x = 5" ]
        ) ,
        ])
    def test_where_statement( mock_db, input, expected):
        mock_db.__init__.return_value = None
        assert expected == mock_db.where( input[ "key"] , input[ "value" ] )._condition_list
    

    It fails on the line where I set the return_value to None - issue is :

        def test_where_statement( mock_db, input, expected):
    >       mock_db.__init__.return_value = None
    E       AttributeError: 'instancemethod' object has no attribute 'return_value'
    

    I want to mock only the init function and then be able to run the where function ( which builds the where statement of my query for example ).

    What am I missing?

    • dm03514
      dm03514 over 6 years
      are you patching the Db import on your test file??
    • ghostrider
      ghostrider over 6 years
      @dm03514 what do you mean? I just have from mydb import Db on top of my test file.
    • erhesto
      erhesto over 6 years
      @ghostrider Please check my updated answer.
  • ghostrider
    ghostrider over 6 years
    tried the first one, and I think init was called because it tried to access my db.
  • erhesto
    erhesto over 6 years
    Are you sure that you mock mydb in proper location? The rule of thumb is to mock things where they are imported and not where they are located. So in other words, if you want to test code that is placed in file my_package.my_module that has import mydb you need to patch my_package.my_module.mydb.Db.