getsentry / responses

A utility for mocking out the Python Requests library.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to mock API calls in a different thread

hgducharme opened this issue · comments

Describe the bug

I couldn't quite find an answer online anywhere so I figured I would post my question here. It appears that responses isn't mocking API calls made in separate threads?

Additional context

No response

Version of responses

0.23.1

Steps to Reproduce

import requests
import responses
import threading

def call_api(url):
    response = requests.get(url, stream = True)

    assert response.status_code == 506

class TestClass:
    @responses.activate
    def test_apiCallInThread(self):
        url = "http://twitter.com/api/1/foobar"
        responses.add(
            responses.GET,
            url,
            body = None,
            status = 506,
        )

        thread = threading.Thread(target=call_api, args=(url,))
        thread.start()

Expected Result

For the test to continue on with no errors.

Actual Result

I get the following result saying that not all my requests have been executed.

=================================================== test session starts ====================================================
platform darwin -- Python 3.8.9, pytest-7.1.1, pluggy-1.0.0
rootdir: /Users/hgd/repos/chessAI/test/lichess, configfile: pytest.ini
collected 1 item                                                                                                           

test_TrueMinimal.py .                                                                                                [100%]

==================================================== 1 passed in 0.01s =====================================================
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/hgd/repos/chessAI/test/lichess/test_TrueMinimal.py", line 8, in call_api
    assert response.status_code == 506
AssertionError: assert 200 == 506
 +  where 200 = <Response [200]>.status_code

Hello,
First of all, would be better to provide minimal reproducible code rather than copy of your opinionated code

Second, I would assume that your issue is that you don't call requests within context manager
See https://github.com/getsentry/responses#responses-as-a-context-manager

Ok I've updated the original post with a more minimal example. I am finding that in both cases (responses as a context manager and using @responses.activate) responses is not mocking my API call.

As you can see in the error message above, I am trying to return 506 status code (something random for the sake of the point), but am getting a 200 status code. I think there might be some sort of race condition that wasn't obvious to me. The moment I join to the thread after thread.start() the exception goes away and responses properly mocks the call. I assumed the order of execution would go something along the lines of:

(main thread) -> responses wraps all future code --- mocked response gets added --- new thread starts --- main thread implicitly waits for thread to end --- main thread ends
                                                                                     (new thread) -> executes api call --- ends

It seems this is not the case. I think what may be happening is that the main thread is ending and the responses environment is no longer available to mock API calls? I suppose this means that I need to find a way in my integration test to make sure the main thread doesn't end, which seems a bit odd to do in my context I think. This is my diagnosis of the situation, do you perhaps see something different? Sorry to use the issue page as a rubber duck programming exercise

I would say this is just python behaviour. Threads are isolated and function in sub thread has no idea of environment of the main thread.

Overall, you need to join threads in order to collect results from subthreads.
See https://github.com/getsentry/responses/blob/master/responses/tests/test_multithreading.py

Usually, you should have at least one place in your code where you will join threads. This is the function you need to test.

I may have a look into multithreading story more when get time, but don't think it will bring new results

Sweet, thanks for the info. I'll chalk this one up to not seeing the race condition here and will figure out a way to handle that in my tests