pythonmifarepyscard

Mocking a smart card response using pyscard


I'm trying to mock a response in a small function using some pyscard attributes.

My function:

def card_reader():
    # request any card type
    cardtype = AnyCardType()
    cardrequest = CardRequest(timeout=25, cardType=cardtype)
    cardservice = cardrequest.waitforcard()

    # attach the console tracer
    observer = ConsoleCardConnectionObserver()
    cardservice.connection.addObserver(observer)

    # connect to the card and perform a few transmits
    cardservice.connection.connect()

    try:
        response, sw1, sw2 = cardservice.connection.transmit(ADPU)

    except SWException as e:
        print(str(e))

    cardservice.connection.disconnect()

And my mock:

@patch("my_module.temp_card_reader.CardRequest")
def test_card_reader(mock_card_request):
    mock_card_request.return_value.connection.transmit.return_value = (200, 201, 203)
    result = card_reader()

Using my test, I can mock until cardservice.connection.connect(), but I can't go forward to return a value that I want.


Solution

  • I've always been intrigued by Mocks, MagicMocks and my apparent lack of knowledge about them 😂, that's why I was curious about this.

    I could totally be wrong, but I think what is happening is the following:

    1. The mocked class is CardRequest.
    2. At a certain point, there's this cardservice = cardrequest.waitforcard()
    3. Because nothing (no side-effect, no return_value... ) for .waitforcard() is defined, the MagicMock() says "Oh... ok... I guess I'll return a blank (default?) MagicMock here" (meaning: cardservice is a default MagicMock)
    4. And when you try to call an unspecified method on a MagicMock, it will keep returning MagicMocks.

    I think (I've tested but I'm not super-confident, since I'm missing the actual code) that the solution is to tell the CardRequest mock something along the lines of: "Hey, when they call .waitforcard(), don't return a blank (default?) MagicMock, but rather THIS mock_cardservice mock which knows how to handle the .transmit method"

    So maaaybe mocking a cardservice would work:

    @patch("my_module.temp_card_reader.CardRequest")
    def test_card_reader(mock_card_request):
        mock_cardservice = MagicMock()
        mock_cardservice.connection.transmit.return_value = (200, 201, 203)
        mock_card_request.return_value.waitforcard.return_value = mock_cardservice
        result = card_reader()
    

    With this, I've been able to see 200 201 203 if I do a print right after cardservice.connection.transmit(ADPU):

        response, sw1, sw2 = cardservice.connection.transmit(ADPU)
        print(response, sw1, sw2)