How to Test Monorepo in 3 Layers

You already have a monorepo, you have at least 2 packages, autoloaded with composer and splitting works. Now you're about to set up testing and code quality tools.

How to make testing so tight no bug can escape?

Is this your first time with monorepo? Check gomonorepo.org to get into this topic fast.

There are 3 layers you test your monorepo in. Often projects go just a few of them:

I'm not sure why the last one is often skipped. Surprisingly, it's very easy to setup - add .travis.yml and enable the repository testing on Travis.

Now you know, what testing layers there are. It's time to look why each layer is important.

1. Testing Monorepo

/packages
    /first-package
    /second-package
.travis.yml
phpunit.xml

Why is it Important?

Monorepo is more complex than the classic package. The developers who use it needs to study more nested directories, special rules and exceptions he didn't have to before. He's already exhausted by learning all this and he's barely some energy left to contribute.

That's why your monorepo workflow has to be as simple as possible.

Testing should be as easy as:

vendor/bin/phpunit

One run and I we can see test are passing or failing. Must have.

2. Testing Standalone Packages in Monorepo

 /packages
     /first-package
+        phpunit.xml
     /second-package
+        phpunit.xml
 .travis.yml
 phpunit.xml

In this layer, each package has own PHPUnit setup. It still uses root vendor/autoload.php, but the testing scope is more similar to standalone package testing. If's faking after split testing for poor people.

vendor/bin/phpunit packages/first-package
vendor/bin/phpunit packages/second-package

Why is it Important?

PHPUnit has own autoloading so it autoloads tests without relying on your composer.json. It's for historical reasons and also the fact, it's not standard to autoload test files or even user PSR-4 naming in them.


Use PSR-4 in your tests:

PHPStan and Rector are already forcing you to do it because they need to know the exact class type of every element to works correctly.

Thank you!


Back to testing...

So when you run e.g. vendor/bin/phpunit packages, you basically tell the PHPUnit autoload packages directory.

What happens, when:

It will silently pass. Monorepo has many classes you work with and some test classes can be accidentally reused in another package. Your test run says it passes, even though it's broken.

You'll find out eventually when second-package is downloaded and break the code to somebody but isn't automated testing suppose to prevent that?

3. After Split testing

 /packages
     /first-package
         phpunit.xml
+        .travis.yml
     /second-package
         phpunit.xml
+        .travis.yml
 .travis.yml
 phpunit.xml

In each .travis.yml you put script to run tests:

script:
    - vendor/bin/phpunit

It will trigger standalone Travis for each package after splitting the monorepo:

Why is it Important?

This is like a double condom with birth control - the best quality testing you can get. It's almost identical with real use when programmer downloads a package by our-project/second-package.

It will download:


I think you've figured out by now the why by seeing ONLY. You can't find this bug in layer 1 or 2.

Our first package uses Doctrine /packages/first-package/composer.json

{
    "name": "our-project/first-package",
    "require": {
        "php": "^7.2",
        "doctrine/orm": "^2.7"
    }
}

At the same time, second-package has this class:

<?php

namespace OurProject\SecondPackage;

use Doctrine\ORM\EntityManagerInterface;

class ProductController
{
    public function __construct(EntityManagerInterface $entityManager)
    {
    }
}

And this composer.json:

{
    "name": "our-project/second-package",
    "require": {
        "php": "^7.2"
    }
}

What happens, when we run one all previous layers?

vendor/bin/phpunit
vendor/bin/phpunit packages/first-package
vendor/bin/phpunit packages/second-package

It will silently pass, because our monorepo has doctrine/orm installed, thanks to dependency in first-package (it's actually propagated by tools to root composer.json).

This is the most common error while developing with monorepo first year. You add dependencies here and there, you add a couple of new packages and code grows and grows.

That's why after split testing is so important. Travis will tell you this instantly.

The Better Your Test Are, The More You Focus on Coding

Of course, you can manage these mutual dependencies by manual testing, in code-reviews, have a tool that will scan the code and composer it to composer.json requirements and so on. Their options are very stressful for developers because they need to automated work manually - imagine you'd check each space on each line instead of using Easy Coding Standard.

So instead of focusing on machines work, just add .travis.yml to each of your packages and let Travis handle that.

Travis has a purpose and you can focus on what you enjoy the most - coding.

Win-win :)


Typo? Fix it, please  and join 49 people who build this website