0

I want to use a mock object to emulate the database on a insert operation.

For example, let's suppose I have a method like insert(1) that calls a db connection object (let's call it db_obj) and performs some insert into mytable (col1) values (%s) where %s is 1.

What I had in mind: create some mock object for db_obj that stores the value of col1, so when db_obj.insert(1) is called, the mocked db_obj stores this col1=1 and, then, I can just get the mocked object col1 value and assert that it's 1 as expected.

Does this approach makes sense? If so, how can I do this using pytest?

Here's an example of what I'm trying

from hub.scripts.tasks.notify_job_module import http_success
from hub.scripts.tasks.notify_job_module import proceed_to
from hub.core.connector.mysql_db import MySql
from unittest.mock import patch
import logging

def proceed_to(conn,a,b,c,d,e):
  conn.run_update('insert into table_abc (a,b,c,d,e) values (%s,%s,%s,%s,%s)',[a,b,c,d,e])

class MySql:
# this is the real conn implementation

    def connect(self):
      # ... create the database connection here

    def run_update(self, query, params=None):
      # ... perform some insert into the database here

class TestMySql:
# this is the mock implementation I want to get rid of

    def connect(self):
      pass

    def run_update(self, query, params=None):
      self.result = params

    def get_result(self):
      return self.result

def test_proceed_to():

    logger = logging.getLogger("")
    conn = TestMySql() ## originally, my code uses MySql() here
    conn.connect()
    proceed_to(conn,1,'2',3,4,5)
    assert conn.get_result()[1] == 4

Please notice that I had to replace MySql() with TestMySql(), so what I've done was to implement my own Mock object manually.

This way it works, but I feel like it's obviously not the best approach here. Why?

Because we're talking about mock objects, the definition of proceed_to is irrelevant here :-) the thing is: I had to implement TestMySql.get_result() and store the data I want in self.result to get the result I want, while MySql itself does not has a get_result() itself!

What I'd like to to is to avoid having to create my own mock object here and use some smarter approach here using unittest.mock

shikida
  • 485
  • 2
  • 10
  • 1
    You can probably do this using `unittest.mock` - pytest has nothing to do with this, except that you may use `pytest-mock` for convenience. Have to tried anything so far? You may need to show a bit of your code and your test if you want to get more specific help here. – MrBean Bremen Feb 23 '22 at 18:30
  • thanks @MrBeanBremen - I've changed the question, providing some example of what I have in mind. thx – shikida Feb 24 '22 at 00:36
  • show the definition for `proceed_to` – gold_cy Feb 24 '22 at 12:29
  • hi @gold_cy - because we're talking about mock objects, the definition of proceed_to is irrelevant here :-) the thing is: I had to implement TestMySql.get_result() and store the data I want in self.result to get the result I want, while MySql itself does not has a get_result() itself! – shikida Feb 24 '22 at 12:45
  • it is relevant and if you don't show some of these function definitions we can't help you – gold_cy Feb 24 '22 at 12:48
  • @gold_cy sure, here it goes. I've added to the question description. thx – shikida Feb 25 '22 at 15:05

1 Answers1

1

What you are testing is basically what arguments run_update is called with. You can just mock the connection and use assert_called_xxx methods on the mock, and if you want to check specific arguments instead of all arguments you can check call_args on the mock. Here is an example that matches your sample code:

@mock.patch("some_module.MySql")
def test_proceed_to(mocked):
    # we need the mocked instance, not the class
    sql_mock = mocked.return_value  
    conn = sql_mock.connect()  # conn is now a mock - you also could have created a mock manually
    proceed_to(conn, 1, '2', 3, 4, 5)
    # assert that the function was called with the correct arguments
    conn.run_update.assert_called_once()
    # conn.run_update.assert_called_once_with() would compare all arguments
    assert conn.run_update.call_args[0][1] == [1, '2', 3, 4, 5]
    # call_args[0] are the positional arguments, so this checks the second positional argument
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46