Skip to content

nidkil/test_app_factory

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Test Factory Pattern

This is a Flask project that has been setup to use the factory pattern. I set it up because I was running was stuck while refactoring one of my projects to use the factory pattern. The code would run normally, but my test cases where failing with the following two errors:

RuntimeError: application not registered on db instance and no application bound to current context

RuntimeError: working outside of application context

Most Flask examples don't use the factory pattern so I spent a lot of time searching around to solve the problem. So I thought I would work it out and share it. Hopefully it saves someone else time.

The problem

Once your project starts to grow, code organization is everything. Flask provides a number of mechanisms for code organization. One of these mechanism's is blueprints. Combined with the factory pattern provides a nice way to structure and organise code.

Another problem that the factory pattern helps solve is circular dependencies.

Getting the factory pattern to work isn't hard. Getting it to work correctly, it turned out, was a little harder. The problem I had was caused in the testing code. In the following section I will briefly explain how to setup and use the factory pattern correctly.

Lessons learned

I have added more code than strictly necessary to show the concept of the factory pattern working as a realistic example.

The structure and contents of this example project is:

    src
    │   .gitignore
    │   readme.md
    │   manage.py
    │   requirements.txt
    ├── instance
    │   sensitive.cfg
    ├── test_app_factory
    │   │   __init__.py
    │   │   appliction.py
    │   │   config.py
    │   │   extensions.py
    │   │   models.py
    │   ├── helpers
    │   │   __init__.py
    │   │   misc.py
    │   ├── module
    │   │   __init__.py
    │   │   viws.py
    │   ├── static
    │   │   favicon-16x16.png
    │   │   favicon-32x32.png
    │   └── templates
    │       index.html
    └── tests
        test_basics.py
    

Okay, what's important to point out here?

The core of the factory pattern is setup in application.py and extensions.py. All extensions are initialized in extensions.py. If you add additional extensions make sure to add them to the import statement in test_app_factory/__init__.py. This is a convent way to shorten import statements.

The actual heavy lifting is done in application.py. Each part of the application initialization is a separate function, which are called by the main function app_factory. This function takes a string, which specifies the environment the configuration should be loaded for. The configuration is defined in config.py.

The factory pattern in application.py looks like this:

    def app_factory(config, name):
        app = Flask(...)

        ...

        return app

The function calls a number of functions that load the configuration settings, extensions, blueprints, etc.

Using the factory is really easy, just use the following call:

    app = app_factory('TST')

To access the app object in modules after the application has been initialized is done using the proxy provided by Flask:

    from flask import current_app as app

Now for the part that was driving met crazy, the testing code. I still do not understand fully why it is the only place in my code that was causing a problem, probably has to do with the way cunittest works. Anyway, to get the factory pattern to work you need to add app_context to specific statements. Here is an example.

    class TestCase(unittest.TestCase):
        @classmethod
        def setUpClass(cls):
            cls.app = app_factory('TST')
    
        def setUp(self):
            with self.app.app_context():
                self.client = app.test_client()
                db.create_all()
    
        def tearDown(self):
            with self.app.app_context():
                db.session.remove()
                db.drop_all()
    
        def test_add_user(self):
            with self.app.app_context():
                db.session.add(User(name='test user', email='test@mail.com'))
                db.session.commit()

Conclusion

Finding good examples isn't always easy. The factory pattern can really help to organize the code and make it more readable and maintainable.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published