0

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.

Joey Fran
  • 598
  • 2
  • 24
  • 1
    Are you getting some sort of error? I'm thinking that your mocked `.transmit` returns a single value (`"my value"`) whereas the actual code seems to be returning a tuple with three elements. Can you do `print(response, sw1, sw2)` in the _actual_ `card_reader` function (right after `.transmit(ADPU)`) and see what those values are? – Savir Oct 24 '22 at 21:06
  • Hello. When I put a tuple in `return_vale`, the mock returns this value: `` ( I put only variable to test this). And I got this error (in test): `ValueError: not enough values to unpack (expected 3, got 0)` – Joey Fran Oct 24 '22 at 21:13
  • 1
    I'm a bit flying blind here and I doubt I can be of much help. But I THINK you're seeing that as a "MagicMock" and not as your string or your tuple because you mocked `cardrequest`. Then the mock runs `cardservice = cardrequest.waitforcard()` and it doesn't know what `.waitforcard()` should return ,and therefore it returns a MagicMock. I **think** this is the behavior of the mocks (think... not sure...) I _suspect_ that you might also need to mock a `cardservice`... somehow. Sorry I can't be of more help. – Savir Oct 24 '22 at 21:22
  • 1
    You helped a lot. Thank you very much! – Joey Fran Oct 25 '22 at 21:55
  • 1
    I was curious myself Glad I helped! – Savir Oct 25 '22 at 21:57

1 Answers1

1

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)
Savir
  • 17,568
  • 15
  • 82
  • 136