15

I'm writing test cases for code that is called via a route under Flask. I don't want to test the code by setting up a test app and calling a URL that hits the route, I want to call the function directly. To make this work I need to mock flask.request and I can't seem to manage it. Google / stackoverflow searches lead to a lot of answers that show how to set up a test application which again is not what I want to do.

The code would look something like this.

somefile.py
-----------
from flask import request

def method_called_from_route():
    data = request.values

    # do something with data here

test_somefile.py
----------------
import unittest
import somefile

class SomefileTestCase(unittest.TestCase):

    @patch('somefile.request')
    def test_method_called_from_route(self, mock_request):
       # want to mock the request.values here

I'm having two issues.

(1) Patching the request as I've sketched out above does not work. I get an error similar to "AttributeError: 'Blueprint' object has no attribute 'somefile'"

(2) I don't know how to exactly mock the request object if I could patch it. It doesn't really have a return_value since it isn't a function.

Again I can't find any examples on how to do this so I felt a new question was acceptable.

foglerit
  • 7,792
  • 8
  • 44
  • 64
wizeowl
  • 466
  • 1
  • 6
  • 13

5 Answers5

8

Try this

test_somefile.py

import unittest
import somefile
import mock

class SomefileTestCase(unittest.TestCase):

    def test_method_called_from_route(self):
        m = mock.MagicMock()
        m.values = "MyData"
        with mock.patch("somefile.request", m):
            somefile.method_called_from_route()

unittest.main()

somefile.py

from flask import request

def method_called_from_route():
    data = request.values
    assert(data == "MyData")

This is going to mock the entire request object. If you want to mock only request.values while keeping all others intact, this would not work.

Hakan Baba
  • 1,897
  • 4
  • 21
  • 37
4

A few years after the question was asked, but this is how I solved this with python 3.9 (other proposed solutions stopped working with python 3.8 see here). I'm using pytest and pytest-mock, but the idea should be the same across testing frameworks, as long as you are using the native unittest.mock.patch in some capacity (pytest-mock essentially just wraps these methods in an easier to use api). Unfortunately, it does require that you set up a test app, however, you do not need to go through the process of using test_client, and can just invoke the function directly.

This can be easily handled by using the Application Factory Design Pattern, and injecting application config. Then, just use the created app's .test_request_context as a context manager to mock out the request object. using .test_request_context as a context manager, gives everything called within the context access to the request object. Here's an example below.

import pytest

from app import create_app

@pytest.fixture
def request_context():
    """create the app and return the request context as a fixture
       so that this process does not need to be repeated in each test
    """
    app = create_app('module.with.TestingConfig')
    return app.test_request_context

def test_something_that_requires_global_request_object(mocker, request_context):
    """do the test thing"""
    with request_context():
        # mocker.patch is just pytest-mock's way of using unittest.mock.patch
        mock_request = mocker.patch('path.to.where.request.is.used')
        # make your mocks and stubs
        mock_request.headers = {'content-type': 'application/json'}
        mock_request.get_json.return_value = {'some': 'json'}
   
    # now you can do whatever you need, using mock_request, and you do not
    # need to remain within the request_context context manager
    run_the_function()
    mock_request.get_json.assert_called_once()
    assert 1 == 1
    # etc.

pytest is great because it allows you to easily setup fixtures for your tests as described above, but you could do essentially the same thing with UnitTest's setUp instance methods. Happy to provide an example for the Application Factory design pattern, or more context, if necessary!

Brady Perry
  • 195
  • 4
  • 14
3

with help of Gabrielbertouinataa on this article: https://medium.com/@vladbezden/how-to-mock-flask-request-object-in-python-fdbc249de504:

code:

def print_request_data():
    print(flask.request.data)

test:
    
flask_app = flask.Flask('test_flask_app')

with flask_app.test_request_context() as mock_context:
    mock_context.request.data = "request_data"
    mock_context.request.path = "request_path"

    print_request_data()
zushe
  • 153
  • 1
  • 7
0

Here is an example of how I dealt with it:

test_common.py module

import pytest
import flask

def test_user_name(mocker):
    # GIVEN: user is provided in the request.headers
    given_user_name = "Some_User"
    request_mock = mocker.patch.object(flask, "request")
    request_mock.headers.get.return_value = given_user_name

    # WHEN: request.header.get method is called
    result = common.user_name()

    # THEN: user name should be returned
    request_mock.headers.get.assert_called_once_with("USERNAME", "Invalid User")
    assert result == given_user_name

common.py module

import flask

def user_name():
    return flask.request.headers.get("USERNAME", "Invalid User")
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
  • I can't see any "mocker" fixer at pytest. Can you explain? – Mahabubur Rahaman Melon Jul 08 '20 at 12:22
  • 1
    @MahabuburRahamanMelon `mocker` is a fixture defined in `pytest-mock`. Just `pip install pytest-mock` and it becomes available to pytest tests. https://pypi.org/project/pytest-mock/ – Duncan Sep 25 '20 at 15:27
  • 1
    i've been trying to implement something like this with a Flask test client but i keep getting `AttributeError: 'Flask' object has no attribute 'request'`. Using the `mocker.patch.object` line as shown above tells me it is out of context :/ – nathanjw Jan 19 '23 at 17:46
-4

What you're trying to do is counterproductive. Following the RFC 2616 a request is:

A request message from a client to a server includes, within the first line of that message, the method to be applied to the resource, the identifier of the resource, and the protocol version in use.

Mocking the Flask request you need to rebuild its structure, what certainly, you will not to want to do!

The best approach should be use something like Flask-Testing or use some recipes like this, and then, test your method.

Mauro Baraldi
  • 6,346
  • 2
  • 32
  • 43