Fool the whole package with pytest and pytest-mock

I have a class that uses a package tmdbsimple

:

movie.py

import tmdbsimple as tmdb

tmdb.API_KEY = '12345'

class Movie():
    def __init__(self, tmdb_id):
        movie = tmdb.Movies(tmdb_id)
        response = movie.info()

        self.tmdb_id = tmdb_id
        self.title = movie.title
        self.vote_average = movie.vote_average
        self.watched = False
*snip*

      

This is all well and good, until I want to test it without relying on the actual connection to TMDb:

test_movie.py

import pytest

import movie
from fake_tmdbsimple import FakeTmdbsimple

@pytest.fixture
def testMovie(mocker):
    mocker.patch.dict('sys.modules', {'tmdbsimple': FakeTmdbsimple()})
    return movie.Movie('1')

def test_creation_of_Movie(testMovie):
    assert isinstance(testMovie, movie.Movie)

def test_properties_of_Movie(testMovie):
    assert testMovie.tmdb_id == 1
    assert testMovie.title == 'Example Movie'
    assert testMovie.vote_average == 10
    assert testMovie.watched == False

      

fake_tmdbsimple.py

class FakeTmdbsimple():
    def __init__(self):
        pass

    class Movies():
        _example_movie_data = {
            1: {
                'title': 'Example Movie',
                'vote_average': 10.0
            }
        }

        def __init__(self, tmdb_id):
            self.tmdb_id = tmdb_id

        def info(self):
            self.title = self._example_movie_data[self.tmdb_id]['title']
            self.vote_average = self._example_movie_data[self.tmdb_id]['vote_average']

    class Find():
        def __init__(self, identifier):
            pass

        def info(self, external_source):
            return {
                'movie_results': [
                    {'id': 1}
                ]
            }

    class Search():
        def __init__(self):
            pass

        def movie(self, query):
            return {
                'results': [
                    {
                        'id': 1
                    }
                ]
            }

      

When the test passes, I get this error:

*snip*
self = <tmdbsimple.movies.Movies object at 0x110779cf8>

    def __init__(self):
>       from . import API_VERSION
E       ImportError: cannot import name 'API_VERSION'

      

The test is still trying to import from real tmdbsimple

! The object testMovie

still gets an instance tmdbsimple.movies.Movies

instead of fake_tmdbsimple.Movies

when called tmdb.Movies()

. I'm sure this is the result of my "not mockery of where the object is used, but where it comes from." However, in this case, using pytest

and pytest-mock

, I don't know how to make the layout where it should.

Am I going to do it right? If so, what do I need to do to successfully cheat the entire packet tmdbsimple

? If not, what is the correct way to test my class Movie()

without actually accessing TMDb?

+3


source to share


1 answer


I had a working implementation of this before, for example:

test_movie.py:



import pytest

from pycoin import movie

@pytest.fixture
def testMovie(fake_tmdbsimple):
    return movie.Movie('1')

@pytest.fixture
def fake_tmdbsimple(monkeypatch):
    monkeypatch.setattr('tmdbsimple.Movies', FakeTmdbsimpleMovies)
    monkeypatch.setattr('tmdbsimple.Find', FakeTmdbsimpleFind)
    monkeypatch.setattr('tmdbsimple.Search', FakeTmdbsimpleSearch)

class FakeTmdbsimpleMovies():
    _example_movie_data = {
        1: {
            'title': 'Example Movie',
            'vote_average': 10.0
        }
    }

    def __init__(self, tmdb_id):
        self.tmdb_id = tmdb_id

    def info(self):
        self.title = self._example_movie_data[self.tmdb_id]['title']
        self.vote_average = self._example_movie_data[self.tmdb_id]['vote_average']

class FakeTmdbsimpleFind():
    def __init__(self, identifier):
        pass

    def info(self, external_source):
        return {
            'movie_results': [
                {'id': 1}
            ]
        }

class FakeTmdbsimpleSearch():
    def __init__(self):
        pass

    def movie(self, query):
        return {
            'results': [
                {
                    'id': 1
                }
            ]
        }

def test_creation_of_Movie(testMovie):
    assert isinstance(testMovie, movie.Movie)

def test_properties_of_Movie(testMovie):
    assert testMovie.tmdb_id == 1
    assert testMovie.title == 'Example Movie'
    assert testMovie.vote_average == 10
    assert testMovie.watched == False

      

This had the advantage of not being required pytest-mock

(and also the advantage of working ...), but it seemed impractical to fix each one Movies

, Find

and Search

individually. This would also require re-implementing the fake classes if I want to test other modules with the fake version tmdbsimple

. It also annoys terminating me in me that I tried to mock the whole package, but can't figure out how.

0


source







All Articles