How to mock the service layer in a python (flask) webapp for unit testing? How to mock the service layer in a python (flask) webapp for unit testing? flask flask

How to mock the service layer in a python (flask) webapp for unit testing?


You can use dependency injection or inversion of control to achieve a code much simpler to test.

replace this:

def addtrans():    ...    # check that both users exist    if not services.user_exists(to_id) or not services.user_exists(from_id):        return "no such users"    ...

with:

def addtrans(services=services):    ...    # check that both users exist    if not services.user_exists(to_id) or not services.user_exists(from_id):        return "no such users"    ...

what's happening:

  • you are aliasing a global as a local (that's not the important point)
  • you are decoupling your code from services while expecting the same interface.
  • mocking the things you need is much easier

e.g.:

class MockServices:    def user_exists(id):        return True

Some resources:


You can patch out the entire services module at the class level of your tests. The mock will then be passed into every method for you to modify.

@patch('routes.services')class MyTestCase(unittest.TestCase):    def test_my_code_when_services_returns_true(self, mock_services):        mock_services.user_exists.return_value = True        self.assertIn('success', routes.addtrans())    def test_my_code_when_services_returns_false(self, mock_services):        mock_services.user_exists.return_value = False        self.assertNotIn('success', routes.addtrans())

Any access of an attribute on a mock gives you a mock object. You can do things like assert that a function was called with the mock_services.return_value.some_method.return_value. It can get kind of ugly so use with caution.


I would also raise a hand for using dependency injection for such needs. You can use Dependency Injector to describe structure of your application using inversion of control container(s) to make it look like this:

"""Example of dependency injection in Python."""import loggingimport sqlite3import boto3import example.mainimport example.servicesimport dependency_injector.containers as containersimport dependency_injector.providers as providersclass Core(containers.DeclarativeContainer):    """IoC container of core component providers."""    config = providers.Configuration('config')    logger = providers.Singleton(logging.Logger, name='example')class Gateways(containers.DeclarativeContainer):    """IoC container of gateway (API clients to remote services) providers."""    database = providers.Singleton(sqlite3.connect, Core.config.database.dsn)    s3 = providers.Singleton(        boto3.client, 's3',        aws_access_key_id=Core.config.aws.access_key_id,        aws_secret_access_key=Core.config.aws.secret_access_key)class Services(containers.DeclarativeContainer):    """IoC container of business service providers."""    users = providers.Factory(example.services.UsersService,                              db=Gateways.database,                              logger=Core.logger)    auth = providers.Factory(example.services.AuthService,                             db=Gateways.database,                             logger=Core.logger,                             token_ttl=Core.config.auth.token_ttl)    photos = providers.Factory(example.services.PhotosService,                               db=Gateways.database,                               s3=Gateways.s3,                               logger=Core.logger)class Application(containers.DeclarativeContainer):    """IoC container of application component providers."""    main = providers.Callable(example.main.main,                              users_service=Services.users,                              auth_service=Services.auth,                              photos_service=Services.photos)

Having this will give your a chance to override particular implementations later:

Services.users.override(providers.Factory(example.services.UsersStub))

Hope it helps.