简体   繁体   中英

Mocking Classes in Python with unittest

I'm trying to make a mock of a class with unittest.mock.

I have the following structure of packages and modules:

  • services
    • service_one.py
  • repository
    • repository_mongodb.py
  • test
    • services
      • test_service_one.py

The class repository_mongodb.py is used inside the class service_one.py (by importing the class).

This is the code for the files.

File repository_mongodb.py

class RepositoryMongoDB:

    def __init__(self):
        self.library = []

    def save(self, thing):
        self.library.append(thing)
        return True

File service_one.py

from repository.RepositoryBookList import RepositoryBookList

class ServiceRodri:

    def __init__(self):
        self.repository = RepositoryMongoDB()

    def save_service(self, thing):
        # Do validation or something else
        return self.repository.save(thing)

Now I want to try to make a mock of the class ServiceRodri , and here's what I do.

import unittest
from unittest.mock import patch
from service.service_one import ServiceOne

class ServiceOneTest(unittest.TestCase):

    def setUp(self):
        self.service_one = ServiceOne()

    @patch('service.service_one.RepositoryMongoBD')
    def test_get_one_book_if_exists_decorator(self, mock_repo):
        mock_repo.save.return_value = "call mock"
        result = self.serviceRodri.save_service("")
        self.assertEquals("call mock", result)

I want that when I call the method "save" of the class RepositoryMongoBD to return the result I'm assigned to it. But this doesn't happen.

I've also tried to do it this way.

    @patch('repository.repository_mongodb.RepositoryMongoDB')
    def test_get_one_book_if_exists_decorator(self, mock_repo):
        mock_repo.save.return_value = "call mock"
        result = self.serviceRodri.save_service("")
        self.assertEquals("call mock", result)

But it doesn't work either.

But if I try to mock the function save() this way.

    @patch('service.service_one.RepositoryMongoDB.save')
    def test_get_one_book_if_exists_decorator_2(self, mock_repo):
        mock_repo.return_value = "call mock"
        result = self.serviceRodri.save_service("")
        self.assertEquals("call mock", result)

Works correctly!!! I understand what it's doing is when the call save() is found in service_one module, it is replaced by the mock.

What would be the right way to do it? (and the best way)

I'm a begginer in the world of python. I have searched and read many post, but all the examples shown are very easy (like sum() method). I've tested in other languages but never in Python.

If you insist on using patch , your final attempt is the right way.

The reason the previous attempts don't work is because the patch is applied after the import, so you're not patching the object you think you are.

I've been working with patch for a long time and this still bites me occasionally. So I recommended using simple constructor based dependency injection.

In service_one

class ServiceOne:

    def __init__(self, respository):
        self.repository

Initialize this with service_rodri = ServiceRodri(RepositoryMongoDB()) , maybe in the __init__ file or something. Then in your test, you can create this mock in your setup.

class ServiceOneTest(unittest.TestCase):

    def setUp(self):
        self.repository = MagicMock()
        self.service_one = ServiceOne(self.repository)

NB patching vs. dependency injection:

Patching will also couple the tests to the import structure of your program. This makes safe refactoring that alters the structure of your modules strictly more difficult. It's best used with legacy code when you need to get some tests in place before making changes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM