Skip to content

Running tests and linting

Your business logic should of course be covered by unit tests. When generating code using lime-project generate the result includes boilerplate tests, where you can add your own tests. For Python we are using pytest and our own pytest plugin lime-test. For Web Components we are using Stencils test framework

In a Lime Project we are also using linting tools to catch common code mistakes and follow a shared set of formatting rules. In Python we are using flake8 and for Web Components we are using tslint

Info

Our standard CI toolchain will fail any build containing any test or lint error.

Python

Running Python tests

To run Python tests run

poetry run pytest .

You can also configure VSCode to discover and run Python tests.

Linting and formatting Python

To lint your Python code:

poetry run flake8 .

You can also configure VSCode to automatically lint your code

We are also including autopep8 as an Python formatter, which will automatically format your code. You can configure VSCode to do this for you.

The lime-test framework

lime-test helps you write tests for code that makes use of the Lime CRM platform, such as plugins or non-core packages. It's implemented as a plugin to pytest, which gives you a set of ready made fixtures that you can use for the more basic types of tests for Lime. For more control, you are encouraged to use the available helper functions that allows you to create more pointed test code.

The following example uses the core_app fixture, which loads a complete LimeApplication <lime-application:lime_application.application.LimeApplication> with a set of lime types defined that closely matches the current state of the lime-core database. It also loads a complete database to memory that is disregarded after the test ends to ensure that multiple tests don't interfere with each other.

The code we want to test:

def average(limeobjects, propname):
    values = [o.get_property(propname).value for o in limeobjects]
    return sum(values) / len(values)
The code we can use to test it:

def test_average(core_app):
    Deal = core_app.limetypes.deal
    deals = [
        Deal(value=2),
        Deal(value=4)
    ]
    assert average(deals, 'value') == 3

Fixtures

lime-test is implemented as a plugin to pytest, which means that as soon as it's installed, it will contain fixtures to be used to set up the conditions for your tests more quickly. These fixtures are more often than not to be considered a starting point, and you'll most likely find yourself in need of more fine grained control over your test setups.

To use a fixture in your tests, do this:

def test_my_app(core_app):
    uow = core_app.unit_of_work()
    uow.add(core_app.limetypes.company(name='acme'))
    uow.commit()

    assert len(list(core_app.limetypes.company.get_all())) == 1

Defining data structures

In order for your tests to be manageable, it's essential to distinctly be able to define and read what types are expected of your code. To help with this, lime-test makes use of lime-core's ability to express data structures as dicts or YAML-documents.

The following shows an example of a fixture that sets up an empty database with a coworker and a company that relates to each other:

@pytest.fixture
def my_database(empty_database):
    types = """
        company:
            name: string
            rating:
                type: option
                options:
                    - poor
                    - good
                    - excellent
            responsible:
                type: belongsto
                related: coworker
                backref: companies
        coworker:
            company:
                type: hasmany
                related: company
                backref: responsible
    """

    limetypes = lime_type.create_limetypes_from_dsl(types)
    lime_test.db.add_limetypes(empty_database, limetypes)
    return empty_database
For more detailed information see lime_type.create_limetypes_from_dsl. It's also possible to define object instances to use in your tests by using lime_type.create_limeobjects_from_dsl.

In-memory Web Application

lime-test enables you to start up a complete web application in memory that you can make requests to to ensure that your endpoints work as they should. It builds on the in memory database to create a complete environment for your endpoints that is created for your specific test method, and teared down immediately afterwards.

It's an implementation of Flasks's test server

A simple example

Given the following endpoint:

class DealAverage(webserver.LimeResource):
    def get(self, companyid):
        company = self.application.limetypes.company.get(companyid)
        avg = averagelib.average(company.properties.deal.fetch(), 'value')

        return {'average': avg}


api.add_resource(DealAverage, '/dealaverage/<int:companyid>')

We can write the following test:

def test_calculate_average_for_deals(webapp, limeapp):
    Company = limeapp.limetypes.company
    Deal = limeapp.limetypes.deal

    uow = limeapp.unit_of_work()

    acme = Company(name='acme')
    acme_idx = uow.add(acme)

    deals = [
        Deal(name='deal1', value=42),
        Deal(name='deal2', value=128),
    ]
    for d in deals:
        acme.properties.deal.attach(d)
        uow.add(d)

    res = uow.commit()

    acme = res[acme_idx]

    res = webapp.get('/myapp/acme_solution/dealaverage/{acme.id}'
                     .format(acme=acme))

    json_response = json.loads(res.data.decode('utf-8'))
    assert res.status_code == 200
    assert json_response == {'average': 85.0}