Skip to main content
Became Hot Network Question
edited tags
Link
toolic
  • 22k
  • 6
  • 33
  • 271

Python3 storing Storing functions for easier testing and mock API data for testing

Source Link
Luca
  • 109
  • 4

Python3 storing functions for easier testing and mock API data for testing

Project collects football data from external API.

from app.teams.get_team_statistics import GetTeamStatistics


class TeamStatisticsService:

    LEAGUE = 'premier league'
    COUNTRY = 'england'

    def __init__(self, statistics_call=None):
        """
        Store a request function used to make API calls.
        """
        self._statistics_call = statistics_call or GetTeamStatistics()
        self._cache = {}

    def _get_response(self, team: str, season: int) -> dict:
        """Fetch and return the API response dict for a given team and season."""
        key = (team, season)
        if key not in self._cache:
            self._cache[key] = self._statistics_call.get_team_statistics_response(
                team=team, league=self.LEAGUE, country=self.COUNTRY, season=season)['response']
        return self._cache[key]

    def team_form(self, team: str, season: int) -> str:
        return self._get_response(team, season)['form']

Testing the API response:

import pytest
from unittest.mock import patch
from app.teams.team_statistics_service import TeamStatisticsService
from app.teams.get_team_statistics import GetTeamStatistics

FAKE_TEAMS = {"response": [{"team": {"id": 33, "name": "Manchester United"}}]}
FAKE_SEASONS = {"response": [2022]}


# Prevents using the real API
@pytest.fixture(autouse=True)
def mock_external_calls():
    with patch('app.teams.get_team_statistics.get_all_teams', return_value=FAKE_TEAMS), \
            patch('app.teams.get_team_statistics.get_season_availability', return_value=FAKE_SEASONS):
        yield


def fake_make_request(request_url: str):
    return {
        "response": [{
            "league": {
                "id": 39,
                "name": "Premier League",
                "country": "England",
                "season": 2022,
            },
            "team": {
                "id": 33,
                "name": "Manchester United",
            },
            "form": "LLWWWWLWDWDWLWWWWWDLWDWWLDLWWWDWLLWWWW",
            "fixtures": {
                "played": {"home": 19, "away": 19, "total": 38},
                "wins": {"home": 15, "away": 8, "total": 23},
                "draws": {"home": 3, "away": 3, "total": 6},
                "loses": {"home": 1, "away": 8, "total": 9},
            },
            "goals": {
                "for": {
                    "total": {"home": 36, "away": 22, "total": 58},
                    "average": {"home": "1.9", "away": "1.2", "total": "1.5"},
                    "minute": {
                        "0-15": {"total": 6, "percentage": "10.53%"},
                        "16-30": {"total": 8, "percentage": "14.04%"},
                        "31-45": {"total": 10, "percentage": "17.54%"},
                        "46-60": {"total": 10, "percentage": "17.54%"},
                        "61-75": {"total": 9, "percentage": "15.79%"},
                        "76-90": {"total": 10, "percentage": "17.54%"},
                        "91-105": {"total": 4, "percentage": "7.02%"},
                        "106-120": {"total": None, "percentage": None},
                    },
                    "under_over": {
                        "0.5": {"over": 31, "under": 7},
                        "1.5": {"over": 20, "under": 18},
                        "2.5": {"over": 6, "under": 32},
                        "3.5": {"over": 1, "under": 37},
                        "4.5": {"over": 0, "under": 38},
                    },
                },
                "against": {
                    "total": {"home": 10, "away": 33, "total": 43},
                    "average": {"home": "0.5", "away": "1.7", "total": "1.1"},
                    "minute": {
                        "0-15": {"total": 6, "percentage": "13.64%"},
                        "16-30": {"total": 6, "percentage": "13.64%"},
                        "31-45": {"total": 7, "percentage": "15.91%"},
                        "46-60": {"total": 7, "percentage": "15.91%"},
                        "61-75": {"total": 7, "percentage": "15.91%"},
                        "76-90": {"total": 9, "percentage": "20.45%"},
                        "91-105": {"total": 2, "percentage": "4.55%"},
                        "106-120": {"total": None, "percentage": None},
                    },
                    "under_over": {
                        "0.5": {"over": 21, "under": 17},
                        "1.5": {"over": 9, "under": 29},
                        "2.5": {"over": 5, "under": 33},
                        "3.5": {"over": 3, "under": 35},
                        "4.5": {"over": 2, "under": 36},
                    },
                },
            },
            "biggest": {
                "streak": {"wins": 5, "draws": 1, "loses": 2},
                "wins": {"home": "4-1", "away": "0-2"},
                "loses": {"home": "1-2", "away": "7-0"},
                "goals": {
                    "for": {"home": 4, "away": 3},
                    "against": {"home": 2, "away": 7},
                },
            },
            "clean_sheet": {"home": 11, "away": 6, "total": 17},
            "failed_to_score": {"home": 2, "away": 5, "total": 7},
            "penalty": {
                "scored": {"total": 3, "percentage": "100.00%"},
                "missed": {"total": 0, "percentage": "0%"},
                "total": 3,
            },
            "cards": {
                "yellow": {
                    "0-15": {"total": 4, "percentage": "5.13%"},
                    "16-30": {"total": 8, "percentage": "10.26%"},
                    "31-45": {"total": 8, "percentage": "10.26%"},
                    "46-60": {"total": 13, "percentage": "16.67%"},
                    "61-75": {"total": 16, "percentage": "20.51%"},
                    "76-90": {"total": 18, "percentage": "23.08%"},
                    "91-105": {"total": 11, "percentage": "14.10%"},
                    "106-120": {"total": None, "percentage": None},
                },
                "red": {
                    "0-15": {"total": None, "percentage": None},
                    "16-30": {"total": None, "percentage": None},
                    "31-45": {"total": 1, "percentage": "50.00%"},
                    "46-60": {"total": None, "percentage": None},
                    "61-75": {"total": 1, "percentage": "50.00%"},
                    "76-90": {"total": None, "percentage": None},
                    "91-105": {"total": None, "percentage": None},
                    "106-120": {"total": None, "percentage": None},
                },
            },
        }]
    }


TEAM = 'manchester united'
SEASON = 2022


class TestTeamStatisticsService:

    def setup_method(self):
        mock_get_team_stats = GetTeamStatistics(request_fn=fake_make_request)
        self.service = TeamStatisticsService(
            statistics_call=mock_get_team_stats)

    # ------------------------------------------------------------------ #
    # Form                                                                 #
    # ------------------------------------------------------------------ #

    def test_team_form(self):
        assert self.service.team_form(
            TEAM, SEASON) == "LLWWWWLWDWDWLWWWWWDLWDWWLDLWWWDWLLWWWW"

Questions/comments: I'm not sure whether self._statistics_call = statistics_call or GetTeamStatistics() in the test setup is the cleanest way? I aimed to not use real API calls in tests. Are the fixtures the best way to setup the test with the mock data? The cacheing prevents repeated calls if i already have the data, is there a clearer/better way to do this?