Search code examples
pythonunit-testingmocking

Mock instantiated class in class attribute


I'm working on a project that uses a class called Deck to fetch data from an API. This class is instantiated inside another class called Game Manager through its __init__ (it's a blackjack game btw) like this:

server/game_manager.py

import .deck import Deck
  
class GameManager:
      self._deck = Deck()

server/deck.py

class Deck:
      def fetch_cards(self):
          '''Method that gets cards from api'''
          pass

Right now I get an attribute not found error that I can't get around when mocking Deck class to avoid connecting to the API. My test goes in this way:

server/tests/game_manager_test.py

import unittest
from unittest.mock import patch, Mock
from ..game_manager import *

gm = GameManager()

class TestCardDraw(unittest.TestCase):
      @patch("server.game_manager.deck")
      def test_draw_game_start_cards(self, deck_mock):
          deck_mock = Mock()
          deck_mock.fetch_cards.return_value = [7,2]
          self.assertEqual(len(gm._game_state["player_one"]["cards"]), 2)

Attribute Error: module 'server.game_manager' does not the attribute 'deck'

I'd like to ask the community a couple of questions to understand what I'm doing wrong.

  1. Should I mock the imported deck module from server/game_manager.py or the module deck from the module server/deck.py itself?
  2. What's the difference between doing one or the other?
  3. I'm creating an instance of GameManager at the top of my test suites to test some methods from the same instance. I've changed a value of this instance in one of the test suites to get the result I wanted with setUp. Should I mock fetch_cards method for this instance I'm using for tests or should I keep my strategy of mocking for the specific tests I want to make. What's the difference between this two approaches?

Lastly, I've followed the examples in the documentation as well as Stack Overflow answers all around but at this point I feel confused with understanding what to mock and how to mocking. It's always cool to get your answers in code but I'd like to see where are my knowledge gaps in my thought process.


Solution

  • Deck object needs to be patched in the context where it's used.

    Since the GameManager object is instantiated in your test module, that provides the context for the patch.

    You must patch the _deck attribute of the gm object and configure the MagicMock instance to return the list when the fetch_cards method is called.

    gm = GameManager()
    
    class TestCardDraw(unittest.TestCase):
        @patch.object("gm", "_deck")
        def test_draw_game_start_cards(self, deck_mock):
            deck_mock.configure_mock(**{'fetch_cards.return_value': [7, 2]})
            #...
            self.assertEqual(len(gm._game_state["player_one"]["cards"]), 2)