diff --git a/.github/workflows/cs-tests.yml b/.github/workflows/cs-tests.yml
new file mode 100644
index 0000000..3da9965
--- /dev/null
+++ b/.github/workflows/cs-tests.yml
@@ -0,0 +1,46 @@
+on:
+  - push
+
+name: Run phpcs checks
+
+jobs:
+  mutation:
+    name: PHP ${{ matrix.php }}-${{ matrix.os }}
+
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os:
+          - ubuntu-latest
+
+        php:
+          - "8.1"
+          - "8.2"
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Install PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: "${{ matrix.php }}"
+          tools: composer:v2, cs2pr
+          coverage: none
+
+      - name: Determine composer cache directory
+        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+      - name: Cache dependencies installed with composer
+        uses: actions/cache@v3
+        with:
+          path: ${{ env.COMPOSER_CACHE_DIR }}
+          key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+          restore-keys: |
+            php${{ matrix.php }}-composer-
+      - name: Install dependencies with composer
+        run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+      - name: Run phpcs checks
+        run: vendor/bin/phpcs
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
new file mode 100644
index 0000000..74550fc
--- /dev/null
+++ b/.github/workflows/static-analysis.yml
@@ -0,0 +1,46 @@
+on:
+  - push
+
+name: Run static analysis
+
+jobs:
+  mutation:
+    name: PHP ${{ matrix.php }}-${{ matrix.os }}
+
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os:
+          - ubuntu-latest
+
+        php:
+          - "8.1"
+          - "8.2"
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Install PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: "${{ matrix.php }}"
+          tools: composer:v2, cs2pr
+          coverage: none
+
+      - name: Determine composer cache directory
+        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+      - name: Cache dependencies installed with composer
+        uses: actions/cache@v3
+        with:
+          path: ${{ env.COMPOSER_CACHE_DIR }}
+          key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+          restore-keys: |
+            php${{ matrix.php }}-composer-
+      - name: Install dependencies with composer
+        run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+      - name: Run static analysis
+        run: vendor/bin/psalm --no-cache --output-format=github --show-info=false --threads=4
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
new file mode 100644
index 0000000..d2ab8e7
--- /dev/null
+++ b/.github/workflows/unit-tests.yml
@@ -0,0 +1,47 @@
+on:
+  - push
+
+name: Run PHPUnit tests
+
+jobs:
+  mutation:
+    name: PHP ${{ matrix.php }}-${{ matrix.os }}
+
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os:
+          - ubuntu-latest
+
+        php:
+          - "8.1"
+          - "8.2"
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Install PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: "${{ matrix.php }}"
+          tools: composer:v2, cs2pr
+          coverage: none
+
+      - name: Determine composer cache directory
+        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+      - name: Cache dependencies installed with composer
+        uses: actions/cache@v3
+        with:
+          path: ${{ env.COMPOSER_CACHE_DIR }}
+          key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+          restore-keys: |
+            php${{ matrix.php }}-composer-
+
+      - name: Install dependencies with composer
+        run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+      - name: Run PHPUnit tests
+        run: vendor/bin/phpunit --colors=always
diff --git a/.gitignore b/.gitignore
index c4a2a74..4d3fdcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 clover.xml
 coveralls-upload.json
-phpunit.xml
+.phpcs-cache
+.phpunit.result.cache
 
 # Created by .ignore support plugin (hsz.mobi)
 ### JetBrains template
diff --git a/README.md b/README.md
index 58373b6..8e45b4a 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,23 @@
 # dot-navigation
 
 ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-navigation)
-![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-navigation/3.2.0)
+![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-navigation/3.4.0)
 
 [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/issues)
 [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/network)
 [![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/stargazers)
-[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/blob/3.2.0/LICENSE.md)
+[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/blob/3.0/LICENSE.md)
+
+[![SymfonyInsight](https://insight.symfony.com/projects/68b7c728-4cc9-40ac-a3be-cf17f9b2eaf1/big.svg)](https://insight.symfony.com/projects/68b7c728-4cc9-40ac-a3be-cf17f9b2eaf1)
+
 
 Allows you to easily define and parse menus inside templates, configuration based approach.
 
 ## Installation
 
 Run
-```bash
-$ composer require dotkernel/dot-navigation
-```
+
+    composer require dotkernel/dot-navigation
 
 Merge `ConfigProvider` to your application's configuration.
 
@@ -26,59 +28,8 @@ Register `NavigationMiddleware` in your middleware pipe between the routing and
 
 ## Configuration
 
-In your `config/autoload` directory, create a config file
-
-##### navigation.global.php
-```php
-return [
-    'dot_navigation' => [
-        //enable menu item active if any child is active
-        'active_recursion' => true,
-        
-        //map a provider name to its config
-        'containers' => [
-            'default' => [
-                'type' => 'ArrayProvider',
-                'options' => [
-                    'items' => [
-                        [
-                            'options' => [
-                                'label' => 'Menu #1',
-                                'route' => [
-                                    'route_name' => 'home',
-                                    'route_params' => [],
-                                    'query_params' => [],
-                                    'fragment_id' => null,
-                                    'options' => [],
-                                    
-                                    //the below parameters are not used in route generation
-                                    //they are used in finding if a page is active by omitting some parameters from the check
-                                    'ignore_params' => []
-                                ],
-                            ],
-                            'attributes' => [
-                                'name' => 'Menu #1',
-                            ]
-                        ],
-                        [
-                            'options' => [
-                                'label' => 'Menu #2',
-                                'route' => ['route_name' => 'home'/*,...*/],
-                            ],
-                            'attributes' => [
-                                'name' => 'Menu #1',
-                            ]
-                        ]
-                    ],
-                ],
-            ],
-        ],
-        
-        //register custom providers here
-        'provider_manager' => [],
-    ],
-];
-```
+Locate dot-navigation's distributable config file `vendor/dotkernel/dot-navigation/config/autoload/navigation.global.php.dist` and duplicate it in your project as `config/autoload/navigation.global.php`
+
 
 ## Components
 
diff --git a/composer.json b/composer.json
index 3c2a842..ad70a35 100644
--- a/composer.json
+++ b/composer.json
@@ -17,20 +17,26 @@
             "email": "team@dotkernel.com"
         }
     ],
+    "config": {
+        "sort-packages": true,
+        "allow-plugins": {
+            "dealerdirect/phpcodesniffer-composer-installer": true
+        }
+    },
     "require": {
-        "php": "^7.4 || ~8.0.0 || ~8.1.0",
-        "psr/http-message": "^1.0.1",
-        "psr/http-server-middleware": "^1.0.1",
-        "laminas/laminas-servicemanager": "^3.11.2",
+        "php": "~8.1.0 || ~8.2.0",
+        "dotkernel/dot-authorization": "^3.1.0",
+        "dotkernel/dot-helpers": "^3.2.0",
         "laminas/laminas-escaper": "^2.10.0",
+        "laminas/laminas-servicemanager": "^3.11.2",
         "mezzio/mezzio-template": "^2.4.0",
-        "dotkernel/dot-helpers": "^3.2.0"
+        "psr/http-message": "^1.0.1",
+        "psr/http-server-middleware": "^1.0.1"
     },
     "require-dev": {
-        "phpunit/phpunit": "^9.5.20",
-        "squizlabs/php_codesniffer": "^3.6.2",
-        "laminas/laminas-stdlib": "^3.7.1",
-        "dotkernel/dot-authorization": "^3.1.0"
+        "laminas/laminas-coding-standard": "^2.5",
+        "phpunit/phpunit": "^10.2",
+        "vimeo/psalm": "^5.13"
     },
     "autoload": {
         "psr-4": {
@@ -41,5 +47,16 @@
         "psr-4": {
             "DotTest\\Navigation\\": "test/"
         }
+    },
+    "scripts": {
+        "check": [
+            "@cs-check",
+            "@test"
+        ],
+        "cs-check": "phpcs",
+        "cs-fix": "phpcbf",
+        "test": "phpunit --colors=always",
+        "test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
+        "static-analysis": "psalm --shepherd --stats"
     }
 }
diff --git a/navigation.global.php.dist b/config/autoload/navigation.global.php.dist
similarity index 100%
rename from navigation.global.php.dist
rename to config/autoload/navigation.global.php.dist
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 0000000..1efe663
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
+
+    <arg name="basepath" value="."/>
+    <arg name="cache" value=".phpcs-cache"/>
+    <arg name="colors"/>
+    <arg name="extensions" value="php"/>
+    <arg name="parallel" value="80"/>
+
+    <!-- Show progress -->
+    <arg value="p"/>
+
+    <!-- Paths to check -->
+    <file>src</file>
+    <file>test</file>
+
+    <!-- Include all rules from the Laminas Coding Standard -->
+    <rule ref="LaminasCodingStandard"/>
+</ruleset>
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..6de330b
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
+         bootstrap="./vendor/autoload.php"
+         colors="true">
+  <testsuites>
+    <testsuite name="Dot-Navigation Test Suite">
+      <directory>./test</directory>
+    </testsuite>
+  </testsuites>
+  <coverage/>
+  <source>
+    <include>
+      <directory suffix=".php">./src</directory>
+    </include>
+  </source>
+</phpunit>
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
new file mode 100644
index 0000000..915d9e0
--- /dev/null
+++ b/psalm-baseline.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<files psalm-version="5.13.1@086b94371304750d1c673315321a55d15fc59015">
+    <file src="src/Filter/IsAllowedFilter.php">
+        <MissingTemplateParam>
+            <code>IsAllowedFilter</code>
+        </MissingTemplateParam>
+    </file>
+    <file src="src/NavigationContainer.php">
+        <MissingTemplateParam>
+            <code>RecursiveIterator</code>
+        </MissingTemplateParam>
+    </file>
+    <file src="src/Provider/ProviderPluginManager.php">
+        <MissingTemplateParam>
+            <code>ProviderPluginManager</code>
+        </MissingTemplateParam>
+    </file>
+</files>
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..9dd8f07
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<psalm
+    errorLevel="4"
+    resolveFromConfigFile="true"
+    findUnusedCode="false"
+    findUnusedBaselineEntry="true"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="https://getpsalm.org/schema/config"
+    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
+    errorBaseline="psalm-baseline.xml"
+>
+    <projectFiles>
+        <directory name="src" />
+        <ignoreFiles>
+            <directory name="vendor" />
+        </ignoreFiles>
+    </projectFiles>
+</psalm>
diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php
index 698f6fc..185e1e4 100644
--- a/src/ConfigProvider.php
+++ b/src/ConfigProvider.php
@@ -1,11 +1,6 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation;
 
@@ -15,54 +10,43 @@
 use Dot\Navigation\Factory\NavigationServiceFactory;
 use Dot\Navigation\Factory\ProviderPluginManagerFactory;
 use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Provider\Factory;
+use Dot\Navigation\Provider\FactoryInterface;
 use Dot\Navigation\Provider\ProviderPluginManager;
 use Dot\Navigation\Service\Navigation;
 use Dot\Navigation\Service\NavigationInterface;
 use Dot\Navigation\View\NavigationRenderer;
 use Dot\Navigation\View\RendererInterface;
 
-/**
- * Class ConfigProvider
- * @package Dot\Navigation
- */
 class ConfigProvider
 {
-    /**
-     * @return array
-     */
     public function __invoke(): array
     {
         return [
-            'dependencies' => $this->getDependencyConfig(),
-
+            'dependencies'   => $this->getDependencyConfig(),
             'dot_navigation' => [
-
                 'active_recursion' => true,
-
-                'containers' => [],
-
-                'provider_manager' => []
+                'containers'       => [],
+                'provider_manager' => [],
             ],
         ];
     }
 
-    /**
-     * @return array
-     */
     public function getDependencyConfig(): array
     {
         return [
             'factories' => [
-                NavigationOptions::class => NavigationOptionsFactory::class,
+                Navigation::class            => NavigationServiceFactory::class,
+                NavigationMiddleware::class  => NavigationMiddlewareFactory::class,
+                NavigationOptions::class     => NavigationOptionsFactory::class,
+                NavigationRenderer::class    => NavigationRendererFactory::class,
                 ProviderPluginManager::class => ProviderPluginManagerFactory::class,
-                Navigation::class => NavigationServiceFactory::class,
-                NavigationRenderer::class => NavigationRendererFactory::class,
-                NavigationMiddleware::class => NavigationMiddlewareFactory::class,
             ],
-            'aliases' => [
+            'aliases'   => [
+                FactoryInterface::class    => Factory::class,
                 NavigationInterface::class => Navigation::class,
-                RendererInterface::class => NavigationRenderer::class,
-            ]
+                RendererInterface::class   => NavigationRenderer::class,
+            ],
         ];
     }
 }
diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php
index d6926ec..8e51657 100644
--- a/src/Exception/ExceptionInterface.php
+++ b/src/Exception/ExceptionInterface.php
@@ -1,19 +1,9 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Exception;
 
-/**
- * Interface ExceptionInterface
- * @package Dot\Navigation\Exception
- */
 interface ExceptionInterface
 {
-
 }
diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php
index 94cfc34..865c4b4 100644
--- a/src/Exception/InvalidArgumentException.php
+++ b/src/Exception/InvalidArgumentException.php
@@ -1,19 +1,9 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Exception;
 
-/**
- * Class InvalidArgumentException
- * @package Dot\Navigation\Exception
- */
 class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
 {
-
 }
diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php
index 0549de2..ea835b3 100644
--- a/src/Exception/RuntimeException.php
+++ b/src/Exception/RuntimeException.php
@@ -1,19 +1,9 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Exception;
 
-/**
- * Class RuntimeException
- * @package Dot\Navigation\Exception
- */
 class RuntimeException extends \RuntimeException implements ExceptionInterface
 {
-
 }
diff --git a/src/Factory/NavigationMiddlewareFactory.php b/src/Factory/NavigationMiddlewareFactory.php
index 0d91d65..1069078 100644
--- a/src/Factory/NavigationMiddlewareFactory.php
+++ b/src/Factory/NavigationMiddlewareFactory.php
@@ -1,32 +1,33 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Factory;
 
 use Dot\Navigation\NavigationMiddleware;
 use Dot\Navigation\Service\NavigationInterface;
+use Exception;
+use Psr\Container\ContainerExceptionInterface;
 use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
 
-/**
- * Class NavigationMiddlewareFactory
- * @package Dot\Navigation\Factory
- */
 class NavigationMiddlewareFactory
 {
+    public const MESSAGE_MISSING_NAVIGATION = 'Unable to find NavigationInterface in the container';
+
     /**
-     * @param ContainerInterface $container
-     * @param $requestedName
-     * @return NavigationMiddleware
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     * @throws Exception
      */
-    public function __invoke(ContainerInterface $container, $requestedName): NavigationMiddleware
+    public function __invoke(ContainerInterface $container): NavigationMiddleware
     {
-        $navigation = $container->get(NavigationInterface::class);
-        return new $requestedName($navigation);
+        if (! $container->has(NavigationInterface::class)) {
+            throw new Exception(self::MESSAGE_MISSING_NAVIGATION);
+        }
+
+        return new NavigationMiddleware(
+            $container->get(NavigationInterface::class)
+        );
     }
 }
diff --git a/src/Factory/NavigationOptionsFactory.php b/src/Factory/NavigationOptionsFactory.php
index 1915108..71f7826 100644
--- a/src/Factory/NavigationOptionsFactory.php
+++ b/src/Factory/NavigationOptionsFactory.php
@@ -1,31 +1,45 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Factory;
 
 use Dot\Navigation\Options\NavigationOptions;
+use Exception;
+use Psr\Container\ContainerExceptionInterface;
 use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+use function array_key_exists;
+use function is_array;
 
-/**
- * Class NavigationOptionsFactory
- * @package Dot\Navigation\Factory
- */
 class NavigationOptionsFactory
 {
+    public const MESSAGE_MISSING_CONFIG         = 'Unable to find config in the container';
+    public const MESSAGE_MISSING_PACKAGE_CONFIG = 'Unable to find dot-navigation config';
+
     /**
-     * @param ContainerInterface $container
-     * @param $requestedName
-     * @return NavigationOptions
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     * @throws Exception
      */
-    public function __invoke(ContainerInterface $container, $requestedName): NavigationOptions
+    public function __invoke(ContainerInterface $container): NavigationOptions
     {
-        $config = $container->get('config')['dot_navigation'];
-        return new $requestedName($config);
+        if (! $container->has('config')) {
+            throw new Exception(self::MESSAGE_MISSING_CONFIG);
+        }
+        $config = $container->get('config');
+
+        if (
+            ! array_key_exists('dot_navigation', $config)
+            || ! is_array($config['dot_navigation'])
+            || empty($config['dot_navigation'])
+        ) {
+            throw new Exception(self::MESSAGE_MISSING_PACKAGE_CONFIG);
+        }
+
+        return new NavigationOptions(
+            $config['dot_navigation']
+        );
     }
 }
diff --git a/src/Factory/NavigationRendererFactory.php b/src/Factory/NavigationRendererFactory.php
index 64fbdf1..1180500 100644
--- a/src/Factory/NavigationRendererFactory.php
+++ b/src/Factory/NavigationRendererFactory.php
@@ -1,37 +1,47 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Factory;
 
 use Dot\Navigation\Options\NavigationOptions;
 use Dot\Navigation\Service\NavigationInterface;
 use Dot\Navigation\View\NavigationRenderer;
-use Psr\Container\ContainerInterface;
+use Exception;
 use Mezzio\Template\TemplateRendererInterface;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
 
-/**
- * Class NavigationRendererFactory
- * @package Dot\Navigation\Factory
- */
 class NavigationRendererFactory
 {
+    public const MESSAGE_MISSING_NAVIGATION_INTERFACE = 'Unable to find NavigationInterface in the container';
+    public const MESSAGE_MISSING_NAVIGATION_OPTIONS   = 'Unable to find NavigationOptions in the container';
+    public const MESSAGE_MISSING_TEMPLATE_RENDERER    = 'Unable to find TemplateRendererInterface in the container';
+
     /**
-     * @param ContainerInterface $container
-     * @param $requestedName
-     * @return NavigationRenderer
+     * @throws NotFoundExceptionInterface
+     * @throws ContainerExceptionInterface
+     * @throws Exception
      */
-    public function __invoke(ContainerInterface $container, $requestedName): NavigationRenderer
+    public function __invoke(ContainerInterface $container): NavigationRenderer
     {
-        $options = $container->get(NavigationOptions::class);
-        $navigation = $container->get(NavigationInterface::class);
-        $template = $container->get(TemplateRendererInterface::class);
+        if (! $container->has(NavigationInterface::class)) {
+            throw new Exception(self::MESSAGE_MISSING_NAVIGATION_INTERFACE);
+        }
+
+        if (! $container->has(TemplateRendererInterface::class)) {
+            throw new Exception(self::MESSAGE_MISSING_TEMPLATE_RENDERER);
+        }
+
+        if (! $container->has(NavigationOptions::class)) {
+            throw new Exception(self::MESSAGE_MISSING_NAVIGATION_OPTIONS);
+        }
 
-        return new $requestedName($navigation, $template, $options);
+        return new NavigationRenderer(
+            $container->get(NavigationInterface::class),
+            $container->get(TemplateRendererInterface::class),
+            $container->get(NavigationOptions::class)
+        );
     }
 }
diff --git a/src/Factory/NavigationServiceFactory.php b/src/Factory/NavigationServiceFactory.php
index a7a0bf2..4019809 100644
--- a/src/Factory/NavigationServiceFactory.php
+++ b/src/Factory/NavigationServiceFactory.php
@@ -1,11 +1,6 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Factory;
 
@@ -15,33 +10,47 @@
 use Dot\Navigation\Provider\Factory;
 use Dot\Navigation\Provider\ProviderPluginManager;
 use Dot\Navigation\Service\Navigation;
+use Exception;
+use Psr\Container\ContainerExceptionInterface;
 use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
 
-/**
- * Class NavigationServiceFactory
- * @package Dot\Navigation\Factory
- */
 class NavigationServiceFactory
 {
+    public const MESSAGE_MISSING_PLUGIN_MANAGER     = 'Unable to find RouteHelper in the container';
+    public const MESSAGE_MISSING_ROUTE_HELPER       = 'Unable to find ProviderPluginManager in the container';
+    public const MESSAGE_MISSING_NAVIGATION_OPTIONS = 'Unable to find NavigationOptions in the container';
+
     /**
-     * @param ContainerInterface $container
-     * @param $requestedName
-     * @return Navigation
+     * @throws NotFoundExceptionInterface
+     * @throws ContainerExceptionInterface
+     * @throws Exception
      */
-    public function __invoke(ContainerInterface $container, $requestedName): Navigation
+    public function __invoke(ContainerInterface $container): Navigation
     {
-        $routeHelper = $container->get(RouteHelper::class);
-        $authorization = $container->has(AuthorizationInterface::class)
-            ? $container->get(AuthorizationInterface::class)
-            : null;
+        if (! $container->has(RouteHelper::class)) {
+            throw new Exception(self::MESSAGE_MISSING_ROUTE_HELPER);
+        }
+
+        if (! $container->has(ProviderPluginManager::class)) {
+            throw new Exception(self::MESSAGE_MISSING_PLUGIN_MANAGER);
+        }
 
-        $providerFactory = new Factory($container, $container->get(ProviderPluginManager::class));
+        if (! $container->has(NavigationOptions::class)) {
+            throw new Exception(self::MESSAGE_MISSING_NAVIGATION_OPTIONS);
+        }
 
         /** @var NavigationOptions $options */
         $options = $container->get(NavigationOptions::class);
 
-        /** @var Navigation $service */
-        $service = new $requestedName($providerFactory, $routeHelper, $options, $authorization);
+        $service = new Navigation(
+            new Factory($container, $container->get(ProviderPluginManager::class)),
+            $container->get(RouteHelper::class),
+            $options,
+            $container->has(AuthorizationInterface::class)
+                ? $container->get(AuthorizationInterface::class)
+                : null
+        );
         $service->setIsActiveRecursion($options->getActiveRecursion());
 
         return $service;
diff --git a/src/Factory/ProviderPluginManagerFactory.php b/src/Factory/ProviderPluginManagerFactory.php
index 1d337b6..aaa7dc3 100644
--- a/src/Factory/ProviderPluginManagerFactory.php
+++ b/src/Factory/ProviderPluginManagerFactory.php
@@ -1,30 +1,53 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Factory;
 
 use Dot\Navigation\Provider\ProviderPluginManager;
+use Exception;
+use Psr\Container\ContainerExceptionInterface;
 use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+use function array_key_exists;
+use function is_array;
 
-/**
- * Class ProviderPluginManagerFactory
- * @package Dot\Navigation\Factory
- */
 class ProviderPluginManagerFactory
 {
+    public const MESSAGE_MISSING_CONFIG                  = 'Unable to find config in the container';
+    public const MESSAGE_MISSING_PACKAGE_CONFIG          = 'Unable to find dot-navigation config';
+    public const MESSAGE_MISSING_CONFIG_PROVIDER_MANAGER = 'Missing/invalid dot-navigation config: provider_manager';
+
     /**
-     * @param ContainerInterface $container
-     * @return ProviderPluginManager
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     * @throws Exception
      */
-    public function __invoke(ContainerInterface $container)
+    public function __invoke(ContainerInterface $container): ProviderPluginManager
     {
-        $config = $container->get('config')['dot_navigation']['provider_manager'];
+        if (! $container->has('config')) {
+            throw new Exception(self::MESSAGE_MISSING_CONFIG);
+        }
+        $config = $container->get('config');
+
+        if (
+            ! array_key_exists('dot_navigation', $config)
+            || ! is_array($config['dot_navigation'])
+            || empty($config['dot_navigation'])
+        ) {
+            throw new Exception(self::MESSAGE_MISSING_PACKAGE_CONFIG);
+        }
+        $config = $config['dot_navigation'];
+
+        if (
+            ! array_key_exists('provider_manager', $config)
+            || ! is_array($config['provider_manager'])
+        ) {
+            throw new Exception(self::MESSAGE_MISSING_CONFIG_PROVIDER_MANAGER);
+        }
+        $config = $config['provider_manager'];
+
         return new ProviderPluginManager($container, $config);
     }
 }
diff --git a/src/Filter/IsAllowedFilter.php b/src/Filter/IsAllowedFilter.php
index 7937ed9..3d548e6 100644
--- a/src/Filter/IsAllowedFilter.php
+++ b/src/Filter/IsAllowedFilter.php
@@ -1,50 +1,31 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Filter;
 
 use Dot\Navigation\Service\NavigationInterface;
+use RecursiveFilterIterator;
+use RecursiveIterator;
 
-/**
- * Class IsAllowedFilter
- * @package Dot\Navigation\Filter
- */
-class IsAllowedFilter extends \RecursiveFilterIterator
+class IsAllowedFilter extends RecursiveFilterIterator
 {
-    /** @var  NavigationInterface */
-    protected $navigation;
+    protected NavigationInterface $navigation;
 
-    /**
-     * IsAllowedFilter constructor.
-     * @param \RecursiveIterator $iterator
-     * @param NavigationInterface $navigation
-     */
-    public function __construct(\RecursiveIterator $iterator, NavigationInterface $navigation)
+    public function __construct(RecursiveIterator $iterator, NavigationInterface $navigation)
     {
         $this->navigation = $navigation;
         parent::__construct($iterator);
     }
 
-    /**
-     * @return bool
-     */
     public function accept(): bool
     {
         return $this->navigation->isAllowed($this->current());
     }
 
-    /**
-     * @return IsAllowedFilter
-     */
     public function getChildren(): IsAllowedFilter
     {
-        /** @var \RecursiveIterator $innerIterator */
+        /** @var RecursiveIterator $innerIterator */
         $innerIterator = $this->getInnerIterator();
         return new self($innerIterator->getChildren(), $this->navigation);
     }
diff --git a/src/NavigationContainer.php b/src/NavigationContainer.php
index 409c7e1..7b4fec4 100644
--- a/src/NavigationContainer.php
+++ b/src/NavigationContainer.php
@@ -1,191 +1,129 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation;
 
-/**
- * Class Container
- * @package Dot\Navigation
- */
-class NavigationContainer implements \RecursiveIterator
-{
-    /**
-     * Index of current active child
-     * @var int
-     */
-    protected $index = 0;
+use RecursiveIterator;
+use RecursiveIteratorIterator;
 
-    /**
-     * Child nodes
-     * @var array
-     */
-    protected $children = [];
+use function count;
+
+class NavigationContainer implements RecursiveIterator
+{
+    protected int $index      = 0;
+    protected array $children = [];
 
-    /**
-     * NavigationContainer constructor.
-     * @param array $pages
-     */
     public function __construct(array $pages = [])
     {
         $this->addPages($pages);
     }
 
     /**
-     * @param array $pages
+     * @param Page[] $pages
      */
-    public function addPages(array $pages)
+    public function addPages(array $pages): void
     {
         foreach ($pages as $page) {
             $this->addPage($page);
         }
     }
 
-    /**
-     * @param Page $page
-     */
-    public function addPage(Page $page)
+    public function addPage(Page $page): void
     {
         $this->children[] = $page;
     }
 
-    /**
-     * @return NavigationContainer
-     */
     public function current(): NavigationContainer
     {
         return $this->children[$this->index];
     }
 
-    /**
-     * Increment current position to the next element
-     */
-    public function next()
+    public function next(): void
     {
         $this->index++;
     }
 
-    /**
-     * @return int
-     */
     public function key(): int
     {
         return $this->index;
     }
 
-    /**
-     * @return bool
-     */
     public function valid(): bool
     {
         return isset($this->children[$this->index]);
     }
 
-    /**
-     * Reset position to the first element
-     */
-    public function rewind()
+    public function rewind(): void
     {
         $this->index = 0;
     }
 
-    /**
-     * @return bool
-     */
     public function hasChildren(): bool
     {
         return count($this->children) > 0;
     }
 
-    /**
-     * @return NavigationContainer
-     */
     public function getChildren(): NavigationContainer
     {
         return $this->children[$this->index];
     }
 
-    /**
-     * Find a single child by attribute
-     *
-     * @param string $attribute
-     * @param mixed $value
-     * @return Page|null
-     */
-    public function findOneByAttribute(string $attribute, $value): ?Page
+    public function findOneByAttribute(string $attribute, mixed $value): ?Page
     {
-        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
+        $iterator = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST);
+
         /** @var Page $page */
         foreach ($iterator as $page) {
             if ($page->getAttribute($attribute) === $value) {
                 return $page;
             }
         }
+
         return null;
     }
 
-    /**
-     * Find all children by attribute
-     *
-     * @param string $attribute
-     * @param mixed $value
-     * @return array
-     */
-    public function findByAttribute(string $attribute, $value): array
+    public function findByAttribute(string $attribute, mixed $value): array
     {
-        $result = [];
-        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
+        $result   = [];
+        $iterator = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST);
 
         /** @var Page $page */
         foreach ($iterator as $page) {
-            if ($page->getAttribute($attribute) == $value) {
+            if ($page->getAttribute($attribute) === $value) {
                 $result[] = $page;
             }
         }
+
         return $result;
     }
 
-    /**
-     * Finds a single child by option.
-     *
-     * @param string $option
-     * @param mixed $value
-     * @return Page|null
-     */
-    public function findOneByOption(string $option, $value): ?Page
+    public function findOneByOption(string $option, mixed $value): ?Page
     {
-        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
+        $iterator = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST);
+
         /** @var Page $page */
         foreach ($iterator as $page) {
-            if ($page->getOption($option) == $value) {
+            if ($page->getOption($option) === $value) {
                 return $page;
             }
         }
+
         return null;
     }
 
-    /**
-     * Finds all children by option.
-     *
-     * @param string $option
-     * @param mixed $value
-     * @return array
-     */
-    public function findByOption(string $option, $value): array
+    public function findByOption(string $option, mixed $value): array
     {
-        $result = [];
-        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
+        $result   = [];
+        $iterator = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST);
+
         /** @var Page $page */
         foreach ($iterator as $page) {
-            if ($page->getOption($option) == $value) {
+            if ($page->getOption($option) === $value) {
                 $result[] = $page;
             }
         }
+
         return $result;
     }
 }
diff --git a/src/NavigationMiddleware.php b/src/NavigationMiddleware.php
index 9247ac4..dfc2471 100644
--- a/src/NavigationMiddleware.php
+++ b/src/NavigationMiddleware.php
@@ -1,50 +1,29 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation;
 
 use Dot\Navigation\Service\NavigationInterface;
+use Mezzio\Router\RouteResult;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
-use Mezzio\Router\RouteResult;
 
-/**
- * Class NavigationMiddleware
- * @package Dot\Navigation
- */
 class NavigationMiddleware implements MiddlewareInterface
 {
-    /**
-     * @var NavigationInterface
-     */
-    protected $navigation;
+    protected NavigationInterface $navigation;
 
-    /**
-     * NavigationMiddleware constructor.
-     * @param NavigationInterface $navigation
-     */
     public function __construct(NavigationInterface $navigation)
     {
         $this->navigation = $navigation;
     }
 
-    /**
-     * @param ServerRequestInterface $request
-     * @param RequestHandlerInterface $handler
-     * @return ResponseInterface
-     */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        $routeResult = $request->getAttribute(RouteResult::class, null);
-        if ($routeResult) {
+        $routeResult = $request->getAttribute(RouteResult::class);
+        if ($routeResult instanceof RouteResult) {
             $this->navigation->setRouteResult($routeResult);
         }
 
diff --git a/src/Options/NavigationOptions.php b/src/Options/NavigationOptions.php
index a8715b9..dae8698 100644
--- a/src/Options/NavigationOptions.php
+++ b/src/Options/NavigationOptions.php
@@ -1,66 +1,38 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Options;
 
 use Laminas\Stdlib\AbstractOptions;
 
-/**
- * Class NavigationOptions
- * @package Dot\Navigation\Options
- */
 class NavigationOptions extends AbstractOptions
 {
-    /** @var  array */
-    protected $containers;
+    protected array $containers     = [];
+    protected bool $activeRecursion = true;
 
-    /** @var bool */
-    protected $activeRecursion = true;
-
-    /**
-     * NavigationOptions constructor.
-     * @param array $options
-     */
-    public function __construct($options = null)
+    public function __construct(?array $options = null)
     {
         $this->__strictMode__ = false;
         parent::__construct($options);
     }
 
-    /**
-     * @return mixed
-     */
-    public function getContainers()
+    public function getContainers(): array
     {
         return $this->containers;
     }
 
-    /**
-     * @param mixed $containers
-     */
-    public function setContainers($containers)
+    public function setContainers(array $containers): void
     {
         $this->containers = $containers;
     }
 
-    /**
-     * @return boolean
-     */
-    public function getActiveRecursion()
+    public function getActiveRecursion(): bool
     {
         return $this->activeRecursion;
     }
 
-    /**
-     * @param boolean $activeRecursion
-     */
-    public function setActiveRecursion($activeRecursion)
+    public function setActiveRecursion(bool $activeRecursion): void
     {
         $this->activeRecursion = $activeRecursion;
     }
diff --git a/src/Page.php b/src/Page.php
index 61ae17b..aeac98c 100644
--- a/src/Page.php
+++ b/src/Page.php
@@ -1,153 +1,98 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation;
 
-/**
- * Class Page
- * @package Dot\Navigation
- */
+use function count;
+use function is_string;
+
 class Page extends NavigationContainer
 {
-    /**
-     * @var array
-     */
-    protected $attributes = [];
-
-    /**
-     * @var array
-     */
-    protected $options = [];
-
-    /**
-     * @var null|Page
-     */
-    protected $parent;
-
-    /**
-     * @return bool
-     */
+    protected ?Page $parent     = null;
+    protected array $attributes = [];
+    protected array $options    = [];
+
     public function hasParent(): bool
     {
-        return null !== $this->parent;
+        return $this->parent instanceof Page;
     }
 
-    /**
-     * @return Page|null
-     */
     public function getParent(): ?Page
     {
         return $this->parent;
     }
 
-    /**
-     * @param NavigationContainer $parent
-     */
-    public function setParent(NavigationContainer $parent)
+    public function setParent(Page $parent): void
     {
         $this->parent = $parent;
     }
 
-    /**
-     * @param Page $page
-     */
-    public function addPage(Page $page)
+    public function addPage(Page $page): void
     {
         $page->setParent($this);
         parent::addPage($page);
     }
 
-    /**
-     * @param string $option
-     * @param mixed $value
-     */
-    public function setOption(string $option, $value)
+    public function setOption(string $option, mixed $value): void
     {
         $this->options[$option] = $value;
     }
 
-    /**
-     * @return array
-     */
     public function getOptions(): array
     {
         return $this->options;
     }
 
-    /**
-     * @param array $options
-     */
-    public function setOptions(array $options)
+    public function hasOptions(): bool
+    {
+        return count($this->options) > 0;
+    }
+
+    public function setOptions(array $options): void
     {
         $this->options = $options;
     }
 
-    /**
-     * @param string $attribute
-     * @param mixed $value
-     */
-    public function setAttribute(string $attribute, $value)
+    public function setAttribute(string $attribute, mixed $value): void
     {
         $this->attributes[$attribute] = $value;
     }
 
-    /**
-     * @param string $attribute
-     * @return mixed
-     */
-    public function getAttribute(string $attribute)
+    public function getAttribute(string $attribute): mixed
     {
         return $this->attributes[$attribute] ?? null;
     }
 
-    /**
-     * @return array
-     */
     public function getAttributes(): array
     {
         return $this->attributes;
     }
 
-    /**
-     * @param array $attributes
-     */
-    public function setAttributes(array $attributes)
+    public function hasAttributes(): bool
+    {
+        return count($this->attributes) > 0;
+    }
+
+    public function setAttributes(array $attributes): void
     {
         $this->attributes = $attributes;
     }
 
-    /**
-     * @return string
-     */
     public function getName(): ?string
     {
         return $this->getOption('name');
     }
 
-    /**
-     * @param string $option
-     * @return mixed
-     */
-    public function getOption(string $option)
+    public function getOption(string $option): mixed
     {
         return $this->options[$option] ?? null;
     }
 
-    /**
-     * @return string
-     */
     public function getLabel(): string
     {
         $label = $this->getOption('label');
-        if (!is_string($label) || empty($label)) {
-            $label = 'Not defined';
-        }
-        return $label;
+
+        return ! is_string($label) || empty($label) ? 'Not defined' : $label;
     }
 }
diff --git a/src/Provider/ArrayProvider.php b/src/Provider/ArrayProvider.php
index fe5fade..22c3b02 100644
--- a/src/Provider/ArrayProvider.php
+++ b/src/Provider/ArrayProvider.php
@@ -1,48 +1,27 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Provider;
 
 use Dot\Navigation\NavigationContainer;
 use Dot\Navigation\Page;
 
-/**
- * Class ArrayProvider
- * @package Dot\Navigation\Provider
- */
+use function count;
+use function is_array;
+
 class ArrayProvider implements ProviderInterface
 {
-    /**
-     * @var NavigationContainer
-     */
-    protected $container;
-
-    /**
-     * @var array
-     */
-    protected $items = [];
-
-    /**
-     * ArrayProvider constructor.
-     * @param array $options
-     */
-    public function __construct(array $options = null)
+    protected ?NavigationContainer $container = null;
+    protected array $items                    = [];
+
+    public function __construct(?array $options = [])
     {
-        $options = $options ?? [];
         if (isset($options['items']) && is_array($options['items'])) {
             $this->setItems($options['items']);
         }
     }
 
-    /**
-     * @return NavigationContainer
-     */
     public function getContainer(): NavigationContainer
     {
         if ($this->container instanceof NavigationContainer) {
@@ -58,10 +37,6 @@ public function getContainer(): NavigationContainer
         return $this->container;
     }
 
-    /**
-     * @param array $spec
-     * @return Page
-     */
     protected function getPage(array $spec): Page
     {
         $page = new Page();
@@ -83,18 +58,17 @@ protected function getPage(array $spec): Page
         return $page;
     }
 
-    /**
-     * @return array
-     */
     public function getItems(): array
     {
         return $this->items;
     }
 
-    /**
-     * @param array $items
-     */
-    public function setItems(array $items)
+    public function hasItems(): bool
+    {
+        return count($this->items) > 0;
+    }
+
+    public function setItems(array $items): void
     {
         $this->items = $items;
     }
diff --git a/src/Provider/Factory.php b/src/Provider/Factory.php
index 7f4398c..7944eed 100644
--- a/src/Provider/Factory.php
+++ b/src/Provider/Factory.php
@@ -1,44 +1,23 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Provider;
 
 use Dot\Navigation\Exception\RuntimeException;
 use Psr\Container\ContainerInterface;
 
-/**
- * Class Factory
- * @package Dot\Navigation\Provider
- */
-class Factory
+class Factory implements FactoryInterface
 {
-    /** @var  ContainerInterface */
-    protected $container;
+    protected ContainerInterface $container;
+    protected ?ProviderPluginManager $providerPluginManager;
 
-    /** @var  ProviderPluginManager */
-    protected $providerPluginManager;
-
-    /**
-     * Factory constructor.
-     * @param ContainerInterface $container
-     * @param ProviderPluginManager|null $providerPluginManager
-     */
-    public function __construct(ContainerInterface $container, ProviderPluginManager $providerPluginManager = null)
+    public function __construct(ContainerInterface $container, ?ProviderPluginManager $providerPluginManager = null)
     {
-        $this->container = $container;
+        $this->container             = $container;
         $this->providerPluginManager = $providerPluginManager;
     }
 
-    /**
-     * @param array $specs
-     * @return ProviderInterface
-     */
     public function create(array $specs): ProviderInterface
     {
         $type = $specs['type'] ?? '';
@@ -46,16 +25,12 @@ public function create(array $specs): ProviderInterface
             throw new RuntimeException('Undefined navigation provider type');
         }
 
-        $options = $specs['options'] ?? null;
-        return $this->getProviderPluginManager()->get($type, $options);
+        return $this->getProviderPluginManager()->get($type, $specs['options'] ?? null);
     }
 
-    /**
-     * @return ProviderPluginManager
-     */
     public function getProviderPluginManager(): ProviderPluginManager
     {
-        if (!$this->providerPluginManager) {
+        if (! $this->providerPluginManager instanceof ProviderPluginManager) {
             $this->providerPluginManager = new ProviderPluginManager($this->container, []);
         }
 
diff --git a/src/Provider/FactoryInterface.php b/src/Provider/FactoryInterface.php
new file mode 100644
index 0000000..5968781
--- /dev/null
+++ b/src/Provider/FactoryInterface.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dot\Navigation\Provider;
+
+interface FactoryInterface
+{
+    public function create(array $specs): ProviderInterface;
+
+    public function getProviderPluginManager(): ProviderPluginManager;
+}
diff --git a/src/Provider/ProviderInterface.php b/src/Provider/ProviderInterface.php
index 664eb1d..db0689f 100644
--- a/src/Provider/ProviderInterface.php
+++ b/src/Provider/ProviderInterface.php
@@ -1,24 +1,12 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Provider;
 
 use Dot\Navigation\NavigationContainer;
 
-/**
- * Interface ProviderInterface
- * @package Dot\Navigation\Provider
- */
 interface ProviderInterface
 {
-    /**
-     * @return NavigationContainer
-     */
     public function getContainer(): NavigationContainer;
 }
diff --git a/src/Provider/ProviderPluginManager.php b/src/Provider/ProviderPluginManager.php
index bf61a5d..4ea540c 100644
--- a/src/Provider/ProviderPluginManager.php
+++ b/src/Provider/ProviderPluginManager.php
@@ -1,24 +1,15 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Provider;
 
 use Laminas\ServiceManager\AbstractPluginManager;
 use Laminas\ServiceManager\Factory\InvokableFactory;
 
-/**
- * Class ProviderPluginManager
- * @package Dot\Navigation\Provider
- */
 class ProviderPluginManager extends AbstractPluginManager
 {
-    /** @var  string */
+    /** @var string $instanceOf */
     protected $instanceOf = ProviderInterface::class;
 
     /** @var array */
@@ -26,11 +17,12 @@ class ProviderPluginManager extends AbstractPluginManager
         ArrayProvider::class => InvokableFactory::class,
     ];
 
+    /** @var string[] $aliases */
     protected $aliases = [
         'arrayprovider' => ArrayProvider::class,
         'arrayProvider' => ArrayProvider::class,
         'ArrayProvider' => ArrayProvider::class,
-        'array' => ArrayProvider::class,
-        'Array' => ArrayProvider::class,
+        'array'         => ArrayProvider::class,
+        'Array'         => ArrayProvider::class,
     ];
 }
diff --git a/src/Service/Navigation.php b/src/Service/Navigation.php
index 85b5bba..57792b0 100644
--- a/src/Service/Navigation.php
+++ b/src/Service/Navigation.php
@@ -1,11 +1,6 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Service;
 
@@ -15,117 +10,72 @@
 use Dot\Navigation\NavigationContainer;
 use Dot\Navigation\Options\NavigationOptions;
 use Dot\Navigation\Page;
-use Dot\Navigation\Provider\Factory;
-use Dot\Navigation\Provider\ProviderInterface;
+use Dot\Navigation\Provider\FactoryInterface;
 use Mezzio\Router\RouteResult;
+use RecursiveIteratorIterator;
+
+use function array_diff;
+use function array_merge;
+use function spl_object_hash;
+use function sprintf;
 
-/**
- * Class Navigation
- * @package Dot\Navigation\Service
- */
 class Navigation implements NavigationInterface
 {
-    /**
-     * @var NavigationContainer[]
-     */
-    protected $containers = [];
-
-    /**
-     * @var Factory
-     */
-    protected $providerFactory;
-
-    /** @var  RouteHelper */
-    protected $routeHelper;
-
-    /**
-     * @var NavigationOptions
-     */
-    protected $moduleOptions;
-
-    /**
-     * @var AuthorizationInterface
-     */
-    protected $authorization;
-
-    /**
-     * @var RouteResult
-     */
-    protected $routeResult;
-
-    /**
-     * @var array
-     */
-    protected $isActiveCache = [];
-
-    /**
-     * @var array
-     */
-    protected $hrefCache = [];
-
-    /**
-     * @var bool
-     */
-    protected $isActiveRecursion = true;
+    protected FactoryInterface $providerFactory;
+    protected RouteHelper $routeHelper;
+    protected NavigationOptions $moduleOptions;
+    protected ?AuthorizationInterface $authorization;
+    protected ?RouteResult $routeResult = null;
+    protected array $containers         = [];
+    protected array $isActiveCache      = [];
+    protected array $hrefCache          = [];
+    protected bool $isActiveRecursion   = true;
 
-    /**
-     * NavigationService constructor.
-     * @param Factory $providerFactory
-     * @param AuthorizationInterface $authorization
-     * @param RouteHelper $routeHelper
-     * @param NavigationOptions $moduleOptions
-     */
     public function __construct(
-        Factory $providerFactory,
+        FactoryInterface $providerFactory,
         RouteHelper $routeHelper,
         NavigationOptions $moduleOptions,
-        AuthorizationInterface $authorization = null
+        ?AuthorizationInterface $authorization = null
     ) {
-        $this->routeHelper = $routeHelper;
-        $this->authorization = $authorization;
+        $this->routeHelper     = $routeHelper;
+        $this->authorization   = $authorization;
         $this->providerFactory = $providerFactory;
-        $this->moduleOptions = $moduleOptions;
+        $this->moduleOptions   = $moduleOptions;
     }
 
-    /**
-     * @return RouteResult
-     */
-    public function getRouteResult(): RouteResult
+    public function getRouteResult(): ?RouteResult
     {
         return $this->routeResult;
     }
 
-    /**
-     * @param RouteResult $routeResult
-     */
-    public function setRouteResult(RouteResult $routeResult)
+    public function setRouteResult(RouteResult $routeResult): void
     {
         $this->routeResult = $routeResult;
     }
 
-    /**
-     * @return bool
-     */
     public function getIsActiveRecursion(): bool
     {
         return $this->isActiveRecursion;
     }
 
-    /**
-     * @param $isActiveRecursion
-     */
-    public function setIsActiveRecursion(bool $isActiveRecursion)
+    public function setIsActiveRecursion(bool $isActiveRecursion): void
     {
-        if ($isActiveRecursion != $this->isActiveRecursion) {
+        if ($isActiveRecursion !== $this->isActiveRecursion) {
             $this->isActiveRecursion = $isActiveRecursion;
-            $this->isActiveCache = array();
+            $this->isActiveCache     = [];
         }
     }
 
-    /**
-     * @param string $name
-     * @return NavigationContainer
-     */
+    public function getIsActiveCache(): array
+    {
+        return $this->isActiveCache;
+    }
+
+    public function getHrefCache(): array
+    {
+        return $this->hrefCache;
+    }
+
     public function getContainer(string $name): NavigationContainer
     {
         if (isset($this->containers[$name])) {
@@ -133,37 +83,21 @@ public function getContainer(string $name): NavigationContainer
         }
 
         $containersConfig = $this->moduleOptions->getContainers();
-        $containerConfig = $containersConfig[$name] ?? [];
+        $containerConfig  = $containersConfig[$name] ?? [];
         if (empty($containerConfig)) {
             throw new RuntimeException(sprintf('Container `%s` is not defined', $name));
         }
 
-        /** @var ProviderInterface $containerProvider */
-        $containerProvider = $this->providerFactory->create($containerConfig);
-
-        $container = $containerProvider->getContainer();
-        if (!$container instanceof NavigationContainer) {
-            throw new RuntimeException(
-                sprintf(
-                    "Navigation container for name %s is not an instance of %s",
-                    $name,
-                    NavigationContainer::class
-                )
-            );
-        }
+        $containerProvider       = $this->providerFactory->create($containerConfig);
+        $this->containers[$name] = $containerProvider->getContainer();
 
-        $this->containers[$name] = $container;
         return $this->containers[$name];
     }
 
-    /**
-     * @param Page $page
-     * @return bool
-     */
     public function isAllowed(Page $page): bool
     {
         //authorization module is optional, this function will always return true if missing
-        if (!$this->authorization) {
+        if (! $this->authorization instanceof AuthorizationInterface) {
             return true;
         }
 
@@ -179,35 +113,27 @@ public function isAllowed(Page $page): bool
         return true;
     }
 
-    /**
-     * @param Page $page
-     * @return bool
-     */
     public function isActive(Page $page): bool
     {
         $hash = spl_object_hash($page);
         if (isset($this->isActiveCache[$hash])) {
             return $this->isActiveCache[$hash];
         }
+
         $active = false;
-        if ($this->routeResult && $this->routeResult->isSuccess()) {
+        if ($this->routeResult instanceof RouteResult && $this->routeResult->isSuccess()) {
             $routeName = $this->routeResult->getMatchedRouteName();
             $pageRoute = $page->getOption('route');
             if ($pageRoute) {
                 if ($pageRoute['route_name'] === $routeName) {
-                    $reqParams = array_merge($this->routeResult->getMatchedParams(), $_GET);
-                    $pageParams = array_merge(
-                        $pageRoute['route_params'] ?? [],
-                        $pageRoute['query_params'] ?? []
-                    );
+                    $reqParams  = array_merge($this->routeResult->getMatchedParams(), $_GET);
+                    $pageParams = array_merge($pageRoute['route_params'] ?? [], $pageRoute['query_params'] ?? []);
 
-                    $ignoreParams = $pageRoute['ignore_params'] ?? [];
-                    $active = $this->areParamsEqual($pageParams, $reqParams, $ignoreParams);
+                    $active = $this->areParamsEqual($pageParams, $reqParams, $pageRoute['ignore_params'] ?? []);
                 } elseif ($this->isActiveRecursion) {
-                    $iterator = new \RecursiveIteratorIterator($page, \RecursiveIteratorIterator::CHILD_FIRST);
-                    /** @var Page $page */
+                    $iterator = new RecursiveIteratorIterator($page, RecursiveIteratorIterator::CHILD_FIRST);
                     foreach ($iterator as $leaf) {
-                        if (!$leaf instanceof Page) {
+                        if (! $leaf instanceof Page) {
                             continue;
                         }
                         if ($this->isActive($leaf)) {
@@ -222,12 +148,6 @@ public function isActive(Page $page): bool
         return $active;
     }
 
-    /**
-     * @param array $pageParams
-     * @param array $requestParams
-     * @param array $ignoreParams
-     * @return bool
-     */
     protected function areParamsEqual(array $pageParams, array $requestParams, array $ignoreParams): bool
     {
         foreach ($ignoreParams as $unsetKey) {
@@ -239,10 +159,6 @@ protected function areParamsEqual(array $pageParams, array $requestParams, array
         return empty($diff);
     }
 
-    /**
-     * @param Page $page
-     * @return string
-     */
     public function getHref(Page $page): string
     {
         $hash = spl_object_hash($page);
@@ -255,7 +171,7 @@ public function getHref(Page $page): string
             $href = $page->getOption('uri');
         } elseif ($page->getOption('route')) {
             $pageRoute = $page->getOption('route');
-            $href = $this->routeHelper->generateUri($pageRoute);
+            $href      = $this->routeHelper->generateUri($pageRoute);
         }
 
         if ($href) {
diff --git a/src/Service/NavigationInterface.php b/src/Service/NavigationInterface.php
index 64c5a22..001dd02 100644
--- a/src/Service/NavigationInterface.php
+++ b/src/Service/NavigationInterface.php
@@ -1,11 +1,6 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\Service;
 
@@ -13,43 +8,25 @@
 use Dot\Navigation\Page;
 use Mezzio\Router\RouteResult;
 
-/**
- * Interface NavigationInterface
- * @package Dot\Navigation\Service
- */
 interface NavigationInterface
 {
-    /**
-     * @param RouteResult $routeResult
-     */
+    public function getRouteResult(): ?RouteResult;
+
     public function setRouteResult(RouteResult $routeResult);
 
-    /**
-     * @param $isActiveRecursion
-     */
+    public function getIsActiveRecursion(): bool;
+
     public function setIsActiveRecursion(bool $isActiveRecursion);
 
-    /**
-     * @param $name
-     * @return NavigationContainer
-     */
+    public function getIsActiveCache(): array;
+
+    public function getHrefCache(): array;
+
     public function getContainer(string $name): NavigationContainer;
 
-    /**
-     * @param Page $page
-     * @return bool
-     */
     public function isAllowed(Page $page): bool;
 
-    /**
-     * @param Page $page
-     * @return bool
-     */
     public function isActive(Page $page): bool;
 
-    /**
-     * @param Page $page
-     * @return string
-     */
     public function getHref(Page $page): string;
 }
diff --git a/src/View/AbstractNavigationRenderer.php b/src/View/AbstractNavigationRenderer.php
index 8e78aa5..8f14c4d 100644
--- a/src/View/AbstractNavigationRenderer.php
+++ b/src/View/AbstractNavigationRenderer.php
@@ -1,11 +1,6 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\View;
 
@@ -15,47 +10,44 @@
 use Laminas\Escaper\Escaper;
 use Mezzio\Template\TemplateRendererInterface;
 
-/**
- * Class AbstractNavigationHelper
- * @package Dot\Navigation\Helper
- */
+use function implode;
+use function in_array;
+use function is_array;
+use function is_scalar;
+use function is_string;
+use function json_encode;
+use function preg_match;
+use function str_contains;
+use function str_ends_with;
+use function str_replace;
+use function str_starts_with;
+use function strlen;
+use function substr;
+use function trim;
+
 abstract class AbstractNavigationRenderer implements RendererInterface
 {
-    /** @var NavigationInterface */
-    protected $navigation;
-
-    /** @var  string */
-    protected $partial;
-
-    /** @var TemplateRendererInterface */
-    protected $template;
+    protected NavigationInterface $navigation;
+    protected TemplateRendererInterface $template;
+    protected string $partial;
 
-    /**
-     * AbstractNavigationHelper constructor.
-     * @param NavigationInterface $navigation
-     * @param TemplateRendererInterface $template
-     */
     public function __construct(NavigationInterface $navigation, TemplateRendererInterface $template)
     {
         $this->navigation = $navigation;
-        $this->template = $template;
+        $this->template   = $template;
     }
 
-    /**
-     * @param array $attributes
-     * @return string
-     */
     public function htmlAttributes(array $attributes): string
     {
-        $xhtml = '';
+        $xhtml   = '';
         $escaper = new Escaper();
 
         foreach ($attributes as $key => $val) {
             $key = $escaper->escapeHtml($key);
 
-            if (('on' == substr($key, 0, 2)) || ('constraints' == $key)) {
+            if ((str_starts_with($key, 'on')) || ('constraints' === $key)) {
                 // Don't escape event attributes; _do_ substitute double quotes with singles
-                if (!is_scalar($val)) {
+                if (! is_scalar($val)) {
                     // non-scalar data should be cast to JSON first
                     $val = json_encode($val);
                 }
@@ -67,10 +59,10 @@ public function htmlAttributes(array $attributes): string
 
             $val = $escaper->escapeHtmlAttr($val);
 
-            if ('id' == $key) {
+            if ('id' === $key) {
                 $val = $this->normalizeId($val);
             }
-            if (strpos($val, '"') !== false) {
+            if (str_contains($val, '"')) {
                 $xhtml .= " $key='$val'";
             } else {
                 $xhtml .= " $key=\"$val\"";
@@ -79,16 +71,10 @@ public function htmlAttributes(array $attributes): string
         return $xhtml;
     }
 
-    /**
-     * Normalize an ID
-     *
-     * @param  string $value
-     * @return string
-     */
     protected function normalizeId(string $value): string
     {
-        if (strstr($value, '[')) {
-            if ('[]' == substr($value, -2)) {
+        if (str_contains($value, '[')) {
+            if (str_ends_with($value, '[]')) {
                 $value = substr($value, 0, strlen($value) - 2);
             }
             $value = trim($value, ']');
@@ -98,44 +84,27 @@ protected function normalizeId(string $value): string
         return $value;
     }
 
-    /**
-     * @return string|null
-     */
     public function getPartial(): ?string
     {
         return $this->partial;
     }
 
-    /**
-     * @param string $partial
-     */
-    public function setPartial(string $partial)
+    public function setPartial(string $partial): void
     {
         $this->partial = $partial;
     }
 
-    /**
-     * @param string|NavigationContainer $container
-     * @return NavigationContainer
-     */
-    protected function getContainer($container): NavigationContainer
+    protected function getContainer(string|NavigationContainer $container): NavigationContainer
     {
         if (is_string($container)) {
             return $this->navigation->getContainer($container);
-        } elseif (!$container instanceof NavigationContainer) {
+        } elseif (! $container instanceof NavigationContainer) {
             throw new RuntimeException('Container must be a string or an instance of ' . NavigationContainer::class);
         }
 
         return $container;
     }
 
-    /**
-     * Cleans array of attributes based on valid input.
-     *
-     * @param array $input
-     * @param array $valid
-     * @return array
-     */
     protected function cleanAttributes(array $input, array $valid): array
     {
         foreach ($input as $key => $value) {
diff --git a/src/View/NavigationRenderer.php b/src/View/NavigationRenderer.php
index 01bcdd9..3232512 100644
--- a/src/View/NavigationRenderer.php
+++ b/src/View/NavigationRenderer.php
@@ -1,38 +1,22 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\View;
 
 use Dot\Navigation\NavigationContainer;
 use Dot\Navigation\Options\NavigationOptions;
-use Dot\Navigation\Service\Navigation;
+use Dot\Navigation\Service\NavigationInterface;
 use Mezzio\Template\TemplateRendererInterface;
 
-/**
- * Class NavigationRenderer
- * @package Dot\Navigation\View
- */
+use function array_merge;
+
 class NavigationRenderer extends AbstractNavigationRenderer
 {
-    /**
-     * @var NavigationOptions
-     */
-    protected $options;
+    protected NavigationOptions $options;
 
-    /**
-     * NavigationMenu constructor.
-     * @param Navigation $navigation
-     * @param TemplateRendererInterface $template
-     * @param NavigationOptions $options
-     */
     public function __construct(
-        Navigation $navigation,
+        NavigationInterface $navigation,
         TemplateRendererInterface $template,
         NavigationOptions $options
     ) {
@@ -40,13 +24,7 @@ public function __construct(
         parent::__construct($navigation, $template);
     }
 
-    /**
-     * @param string $partial
-     * @param string|NavigationContainer $container
-     * @param array $params
-     * @return string
-     */
-    public function renderPartial($container, string $partial, array $params = []): string
+    public function renderPartial(string|NavigationContainer $container, string $partial, array $params = []): string
     {
         $container = $this->getContainer($container);
 
@@ -59,13 +37,16 @@ public function renderPartial($container, string $partial, array $params = []):
         );
     }
 
-    /**
-     * @param string|NavigationContainer $container
-     * @return string
-     */
-    public function render($container): string
+    public function render(string|NavigationContainer $container, string $template, array $params = []): string
     {
-        // TODO: render a default HTML menu structure
-        return '';
+        $container = $this->getContainer($container);
+
+        return $this->template->render(
+            $template,
+            array_merge(
+                ['container' => $container, 'navigation' => $this->navigation],
+                $params
+            )
+        );
     }
 }
diff --git a/src/View/RendererInterface.php b/src/View/RendererInterface.php
index 73588fa..f686463 100644
--- a/src/View/RendererInterface.php
+++ b/src/View/RendererInterface.php
@@ -1,39 +1,16 @@
 <?php
-/**
- * @see https://github.com/dotkernel/dot-navigation/ for the canonical source repository
- * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com)
- * @license https://github.com/dotkernel/dot-navigation/blob/master/LICENSE.md MIT License
- */
 
-declare(strict_types = 1);
+declare(strict_types=1);
 
 namespace Dot\Navigation\View;
 
 use Dot\Navigation\NavigationContainer;
 
-/**
- * Interface RendererInterface
- * @package Dot\Navigation\View
- */
 interface RendererInterface
 {
-    /**
-     * @param string|NavigationContainer $container
-     * @return string
-     */
-    public function render($container): string;
+    public function render(string|NavigationContainer $container, string $template, array $params = []): string;
 
-    /**
-     * @param $partial
-     * @param array $params
-     * @param string|NavigationContainer $container
-     * @return string
-     */
-    public function renderPartial($container, string $partial, array $params = []): string;
+    public function renderPartial(string|NavigationContainer $container, string $partial, array $params = []): string;
 
-    /**
-     * @param array $attributes
-     * @return string
-     */
     public function htmlAttributes(array $attributes): string;
 }
diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php
new file mode 100644
index 0000000..186347d
--- /dev/null
+++ b/test/ConfigProviderTest.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation;
+
+use Dot\Navigation\ConfigProvider;
+use Dot\Navigation\Factory\NavigationMiddlewareFactory;
+use Dot\Navigation\Factory\NavigationOptionsFactory;
+use Dot\Navigation\Factory\NavigationRendererFactory;
+use Dot\Navigation\Factory\NavigationServiceFactory;
+use Dot\Navigation\Factory\ProviderPluginManagerFactory;
+use Dot\Navigation\NavigationMiddleware;
+use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Provider\Factory;
+use Dot\Navigation\Provider\FactoryInterface;
+use Dot\Navigation\Provider\ProviderPluginManager;
+use Dot\Navigation\Service\Navigation;
+use Dot\Navigation\Service\NavigationInterface;
+use Dot\Navigation\View\NavigationRenderer;
+use Dot\Navigation\View\RendererInterface;
+use PHPUnit\Framework\TestCase;
+
+class ConfigProviderTest extends TestCase
+{
+    protected array $config;
+
+    protected function setup(): void
+    {
+        $this->config = (new ConfigProvider())();
+    }
+
+    public function testHasDependencies(): void
+    {
+        $this->assertArrayHasKey('dependencies', $this->config);
+    }
+
+    public function testDependenciesHasFactories(): void
+    {
+        $this->assertArrayHasKey('factories', $this->config['dependencies']);
+
+        $factories = $this->config['dependencies']['factories'];
+        $this->assertArrayHasKey(Navigation::class, $factories);
+        $this->assertSame(NavigationServiceFactory::class, $factories[Navigation::class]);
+        $this->assertArrayHasKey(NavigationMiddleware::class, $factories);
+        $this->assertSame(NavigationMiddlewareFactory::class, $factories[NavigationMiddleware::class]);
+        $this->assertArrayHasKey(NavigationOptions::class, $factories);
+        $this->assertSame(NavigationOptionsFactory::class, $factories[NavigationOptions::class]);
+        $this->assertArrayHasKey(NavigationRenderer::class, $factories);
+        $this->assertSame(NavigationRendererFactory::class, $factories[NavigationRenderer::class]);
+        $this->assertArrayHasKey(ProviderPluginManager::class, $factories);
+        $this->assertSame(ProviderPluginManagerFactory::class, $factories[ProviderPluginManager::class]);
+    }
+
+    public function testDependenciesHasAliases(): void
+    {
+        $this->assertArrayHasKey('aliases', $this->config['dependencies']);
+
+        $aliases = $this->config['dependencies']['aliases'];
+        $this->assertArrayHasKey(FactoryInterface::class, $aliases);
+        $this->assertSame(Factory::class, $aliases[FactoryInterface::class]);
+        $this->assertArrayHasKey(NavigationInterface::class, $aliases);
+        $this->assertSame(Navigation::class, $aliases[NavigationInterface::class]);
+        $this->assertArrayHasKey(RendererInterface::class, $aliases);
+        $this->assertSame(NavigationRenderer::class, $aliases[RendererInterface::class]);
+    }
+}
diff --git a/test/Exception/InvalidArgumentExceptionTest.php b/test/Exception/InvalidArgumentExceptionTest.php
new file mode 100644
index 0000000..d283fd9
--- /dev/null
+++ b/test/Exception/InvalidArgumentExceptionTest.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Exception;
+
+use Dot\Navigation\Exception\ExceptionInterface;
+use Dot\Navigation\Exception\InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+
+class InvalidArgumentExceptionTest extends TestCase
+{
+    public function testWillReturnCorrectInstances(): void
+    {
+        $exception = new InvalidArgumentException('test');
+        $this->assertInstanceOf(InvalidArgumentException::class, $exception);
+        $this->assertInstanceOf(ExceptionInterface::class, $exception);
+    }
+}
diff --git a/test/Exception/RuntimeExceptionTest.php b/test/Exception/RuntimeExceptionTest.php
new file mode 100644
index 0000000..9d46320
--- /dev/null
+++ b/test/Exception/RuntimeExceptionTest.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Exception;
+
+use Dot\Navigation\Exception\ExceptionInterface;
+use Dot\Navigation\Exception\RuntimeException;
+use PHPUnit\Framework\TestCase;
+
+class RuntimeExceptionTest extends TestCase
+{
+    public function setUp(): void
+    {
+        parent::setUp();
+    }
+
+    public function testWillReturnCorrectInstances(): void
+    {
+        $exception = new RuntimeException('test');
+        $this->assertInstanceOf(RuntimeException::class, $exception);
+        $this->assertInstanceOf(ExceptionInterface::class, $exception);
+    }
+}
diff --git a/test/Factory/NavigationMiddlewareFactoryTest.php b/test/Factory/NavigationMiddlewareFactoryTest.php
new file mode 100644
index 0000000..d075b39
--- /dev/null
+++ b/test/Factory/NavigationMiddlewareFactoryTest.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Factory;
+
+use Dot\Navigation\Factory\NavigationMiddlewareFactory;
+use Dot\Navigation\NavigationMiddleware;
+use Dot\Navigation\Service\NavigationInterface;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+class NavigationMiddlewareFactoryTest extends TestCase
+{
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateMiddlewareWithoutNavigationInterface(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with(NavigationInterface::class)
+            ->willReturn(false);
+
+        $this->expectExceptionMessage(NavigationMiddlewareFactory::MESSAGE_MISSING_NAVIGATION);
+        (new NavigationMiddlewareFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillCreateMiddleware(): void
+    {
+        $container  = $this->createMock(ContainerInterface::class);
+        $navigation = $this->createMock(NavigationInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with(NavigationInterface::class)
+            ->willReturn(true);
+
+        $container->expects($this->once())
+            ->method('get')
+            ->with(NavigationInterface::class)
+            ->willReturn($navigation);
+
+        $middleware = (new NavigationMiddlewareFactory())($container);
+        $this->assertInstanceOf(NavigationMiddleware::class, $middleware);
+    }
+}
diff --git a/test/Factory/NavigationOptionsFactoryTest.php b/test/Factory/NavigationOptionsFactoryTest.php
new file mode 100644
index 0000000..6455d8d
--- /dev/null
+++ b/test/Factory/NavigationOptionsFactoryTest.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Factory;
+
+use Dot\Navigation\Factory\NavigationOptionsFactory;
+use Dot\Navigation\Options\NavigationOptions;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+class NavigationOptionsFactoryTest extends TestCase
+{
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationOptionsWithoutConfig(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(false);
+
+        $this->expectExceptionMessage(NavigationOptionsFactory::MESSAGE_MISSING_CONFIG);
+        (new NavigationOptionsFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationOptionsWithoutPackageConfig(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(true);
+
+        $container->expects($this->once())
+            ->method('get')
+            ->with('config')
+            ->willReturn([]);
+
+        $this->expectExceptionMessage(NavigationOptionsFactory::MESSAGE_MISSING_PACKAGE_CONFIG);
+        (new NavigationOptionsFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillCreateNavigationOptions(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(true);
+
+        $container->expects($this->once())
+            ->method('get')
+            ->with('config')
+            ->willReturn([
+                'dot_navigation' => [
+                    'active_recursion' => true,
+                ],
+            ]);
+
+        $options = (new NavigationOptionsFactory())($container);
+        $this->assertInstanceOf(NavigationOptions::class, $options);
+    }
+}
diff --git a/test/Factory/NavigationRendererFactoryTest.php b/test/Factory/NavigationRendererFactoryTest.php
new file mode 100644
index 0000000..38e91ff
--- /dev/null
+++ b/test/Factory/NavigationRendererFactoryTest.php
@@ -0,0 +1,102 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Factory;
+
+use Dot\Navigation\Factory\NavigationRendererFactory;
+use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Service\NavigationInterface;
+use Dot\Navigation\View\NavigationRenderer;
+use Mezzio\Template\TemplateRendererInterface;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+class NavigationRendererFactoryTest extends TestCase
+{
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationRendererWithoutNavigationInterface(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with(NavigationInterface::class)
+            ->willReturn(false);
+
+        $this->expectExceptionMessage(NavigationRendererFactory::MESSAGE_MISSING_NAVIGATION_INTERFACE);
+        (new NavigationRendererFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationRendererWithoutTemplateRendererInterface(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->method('has')->willReturnMap([
+            [NavigationInterface::class, true],
+            [TemplateRendererInterface::class, false],
+        ]);
+
+        $this->expectExceptionMessage(NavigationRendererFactory::MESSAGE_MISSING_TEMPLATE_RENDERER);
+        (new NavigationRendererFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationRendererWithoutNavigationOptions(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->method('has')->willReturnMap([
+            [NavigationInterface::class, true],
+            [TemplateRendererInterface::class, true],
+            [NavigationOptions::class, false],
+        ]);
+
+        $this->expectExceptionMessage(NavigationRendererFactory::MESSAGE_MISSING_NAVIGATION_OPTIONS);
+        (new NavigationRendererFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationRenderer(): void
+    {
+        $container  = $this->createMock(ContainerInterface::class);
+        $navigation = $this->createMock(NavigationInterface::class);
+        $renderer   = $this->createMock(TemplateRendererInterface::class);
+        $options    = $this->createMock(NavigationOptions::class);
+
+        $container->method('has')->willReturnMap([
+            [NavigationInterface::class, true],
+            [TemplateRendererInterface::class, true],
+            [NavigationOptions::class, true],
+        ]);
+
+        $container->method('get')->willReturnMap([
+            [NavigationInterface::class, $navigation],
+            [TemplateRendererInterface::class, $renderer],
+            [NavigationOptions::class, $options],
+        ]);
+
+        $renderer = (new NavigationRendererFactory())($container);
+        $this->assertInstanceOf(NavigationRenderer::class, $renderer);
+    }
+}
diff --git a/test/Factory/NavigationServiceFactoryTest.php b/test/Factory/NavigationServiceFactoryTest.php
new file mode 100644
index 0000000..fbe9326
--- /dev/null
+++ b/test/Factory/NavigationServiceFactoryTest.php
@@ -0,0 +1,137 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Factory;
+
+use Dot\Authorization\AuthorizationInterface;
+use Dot\Helpers\Route\RouteHelper;
+use Dot\Navigation\Factory\NavigationServiceFactory;
+use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Provider\ProviderPluginManager;
+use Dot\Navigation\Service\Navigation;
+use Mezzio\Template\TemplateRendererInterface;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+class NavigationServiceFactoryTest extends TestCase
+{
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationServiceWithoutRouteHelper(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with(RouteHelper::class)
+            ->willReturn(false);
+
+        $this->expectExceptionMessage(NavigationServiceFactory::MESSAGE_MISSING_ROUTE_HELPER);
+        (new NavigationServiceFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationServiceWithoutProviderPluginManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->method('has')->willReturnMap([
+            [RouteHelper::class, true],
+            [ProviderPluginManager::class, false],
+        ]);
+
+        $this->expectExceptionMessage(NavigationServiceFactory::MESSAGE_MISSING_PLUGIN_MANAGER);
+        (new NavigationServiceFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateNavigationServiceWithoutNavigationOptions(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->method('has')->willReturnMap([
+            [RouteHelper::class, true],
+            [ProviderPluginManager::class, true],
+            [NavigationOptions::class, false],
+        ]);
+
+        $this->expectExceptionMessage(NavigationServiceFactory::MESSAGE_MISSING_NAVIGATION_OPTIONS);
+        (new NavigationServiceFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillCreateNavigationServiceWithoutAuthorizationInterface(): void
+    {
+        $container  = $this->createMock(ContainerInterface::class);
+        $navigation = $this->createMock(RouteHelper::class);
+        $renderer   = $this->createMock(TemplateRendererInterface::class);
+        $options    = $this->createMock(NavigationOptions::class);
+
+        $container->method('has')->willReturnMap([
+            [RouteHelper::class, true],
+            [ProviderPluginManager::class, true],
+            [NavigationOptions::class, true],
+            [AuthorizationInterface::class, false],
+        ]);
+
+        $container->method('get')->willReturnMap([
+            [RouteHelper::class, $navigation],
+            [TemplateRendererInterface::class, $renderer],
+            [NavigationOptions::class, $options],
+            [AuthorizationInterface::class, null],
+        ]);
+
+        $service = (new NavigationServiceFactory())($container);
+        $this->assertInstanceOf(Navigation::class, $service);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillCreateNavigationServiceWithAuthorizationInterface(): void
+    {
+        $container     = $this->createMock(ContainerInterface::class);
+        $navigation    = $this->createMock(RouteHelper::class);
+        $renderer      = $this->createMock(TemplateRendererInterface::class);
+        $options       = $this->createMock(NavigationOptions::class);
+        $authorization = $this->createMock(AuthorizationInterface::class);
+
+        $container->method('has')->willReturnMap([
+            [RouteHelper::class, true],
+            [ProviderPluginManager::class, true],
+            [NavigationOptions::class, true],
+            [AuthorizationInterface::class, true],
+        ]);
+
+        $container->method('get')->willReturnMap([
+            [RouteHelper::class, $navigation],
+            [TemplateRendererInterface::class, $renderer],
+            [NavigationOptions::class, $options],
+            [AuthorizationInterface::class, $authorization],
+        ]);
+
+        $service = (new NavigationServiceFactory())($container);
+        $this->assertInstanceOf(Navigation::class, $service);
+    }
+}
diff --git a/test/Factory/ProviderPluginManagerFactoryTest.php b/test/Factory/ProviderPluginManagerFactoryTest.php
new file mode 100644
index 0000000..e79cfa5
--- /dev/null
+++ b/test/Factory/ProviderPluginManagerFactoryTest.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Factory;
+
+use Dot\Navigation\Factory\ProviderPluginManagerFactory;
+use Dot\Navigation\Provider\ProviderPluginManager;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+class ProviderPluginManagerFactoryTest extends TestCase
+{
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateProviderPluginManagerWithoutConfig(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(false);
+
+        $this->expectExceptionMessage(ProviderPluginManagerFactory::MESSAGE_MISSING_CONFIG);
+        (new ProviderPluginManagerFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateProviderPluginManagerWithoutPackageConfig(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(true);
+
+        $container->expects($this->once())
+            ->method('get')
+            ->with('config')
+            ->willReturn([]);
+
+        $this->expectExceptionMessage(ProviderPluginManagerFactory::MESSAGE_MISSING_PACKAGE_CONFIG);
+        (new ProviderPluginManagerFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillNotCreateProviderPluginManagerWithoutConfigProviderManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(true);
+
+        $container->expects($this->once())
+            ->method('get')
+            ->with('config')
+            ->willReturn([
+                'dot_navigation' => [
+                    'active_recursion' => true,
+                ],
+            ]);
+
+        $this->expectExceptionMessage(ProviderPluginManagerFactory::MESSAGE_MISSING_CONFIG_PROVIDER_MANAGER);
+        (new ProviderPluginManagerFactory())($container);
+    }
+
+    /**
+     * @throws Exception
+     * @throws ContainerExceptionInterface
+     * @throws NotFoundExceptionInterface
+     */
+    public function testWillCreateProviderPluginManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $container->expects($this->once())
+            ->method('has')
+            ->with('config')
+            ->willReturn(true);
+
+        $container->expects($this->once())
+            ->method('get')
+            ->with('config')
+            ->willReturn([
+                'dot_navigation' => [
+                    'provider_manager' => [],
+                ],
+            ]);
+
+        $manager = (new ProviderPluginManagerFactory())($container);
+        $this->assertInstanceOf(ProviderPluginManager::class, $manager);
+    }
+}
diff --git a/test/Filter/IsAllowedFilterTest.php b/test/Filter/IsAllowedFilterTest.php
new file mode 100644
index 0000000..6f9f3ed
--- /dev/null
+++ b/test/Filter/IsAllowedFilterTest.php
@@ -0,0 +1,91 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Filter;
+
+use Dot\Authorization\AuthorizationInterface;
+use Dot\Helpers\Route\RouteHelper;
+use Dot\Navigation\Filter\IsAllowedFilter;
+use Dot\Navigation\NavigationContainer;
+use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Page;
+use Dot\Navigation\Provider\FactoryInterface;
+use Dot\Navigation\Service\Navigation;
+use Dot\Navigation\Service\NavigationInterface;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use RecursiveIterator;
+
+class IsAllowedFilterTest extends TestCase
+{
+    /**
+     * @throws Exception
+     */
+    public function testWillAcceptWithoutAuthorization(): void
+    {
+        $iterator = $this->createMock(RecursiveIterator::class);
+        $factory  = $this->createMock(FactoryInterface::class);
+        $route    = $this->createMock(RouteHelper::class);
+        $options  = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options, null);
+        $filter     = $this->getMockBuilder(IsAllowedFilter::class)
+            ->setConstructorArgs([$iterator, $navigation])
+            ->onlyMethods(['current'])
+            ->getMock();
+        $filter->method('current')->willReturn(new Page());
+
+        $this->assertTrue($filter->accept());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillAcceptWithAuthorization(): void
+    {
+        $iterator      = $this->createMock(RecursiveIterator::class);
+        $factory       = $this->createMock(FactoryInterface::class);
+        $route         = $this->createMock(RouteHelper::class);
+        $options       = $this->createMock(NavigationOptions::class);
+        $authorization = $this->createMock(AuthorizationInterface::class);
+
+        $authorization->expects($this->once())->method('isGranted')->willReturn(true);
+
+        $page = new Page();
+        $page->setOption('permission', 'test');
+        $page->setOption('roles', []);
+
+        $navigation = new Navigation($factory, $route, $options, $authorization);
+        $filter     = $this->getMockBuilder(IsAllowedFilter::class)
+            ->setConstructorArgs([$iterator, $navigation])
+            ->onlyMethods(['current'])
+            ->getMock();
+        $filter->method('current')->willReturn($page);
+
+        $this->assertTrue($filter->accept());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testGetChildren(): void
+    {
+        $navigation = $this->createMock(NavigationInterface::class);
+
+        $filter = new IsAllowedFilter(new NavigationContainer([new Page()]), $navigation);
+        $this->assertInstanceOf(IsAllowedFilter::class, $filter->getChildren());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillCreateFilter(): void
+    {
+        $iterator   = $this->createMock(RecursiveIterator::class);
+        $navigation = $this->createMock(NavigationInterface::class);
+
+        $filter = new IsAllowedFilter($iterator, $navigation);
+        $this->assertInstanceOf(IsAllowedFilter::class, $filter);
+    }
+}
diff --git a/test/NavigationContainerTest.php b/test/NavigationContainerTest.php
new file mode 100644
index 0000000..1b3945d
--- /dev/null
+++ b/test/NavigationContainerTest.php
@@ -0,0 +1,213 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation;
+
+use Dot\Navigation\NavigationContainer;
+use Dot\Navigation\Page;
+use PHPUnit\Framework\TestCase;
+
+class NavigationContainerTest extends TestCase
+{
+    protected int $count = 5;
+
+    public function testWillAddPages(): void
+    {
+        $navigationContainer = new NavigationContainer();
+        $this->assertFalse($navigationContainer->hasChildren());
+        $navigationContainer->addPages([
+            new Page(),
+        ]);
+        $this->assertTrue($navigationContainer->hasChildren());
+    }
+
+    public function testWillAddPage(): void
+    {
+        $navigationContainer = new NavigationContainer();
+        $this->assertFalse($navigationContainer->hasChildren());
+        $navigationContainer->addPage(new Page());
+        $this->assertTrue($navigationContainer->hasChildren());
+    }
+
+    public function testWillReturnCurrentChild(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $currentPage = $navigationContainer->current();
+        $this->assertInstanceOf(Page::class, $currentPage);
+        $this->assertSame($pages[0]->getOption('opt'), $currentPage->getOption('opt'));
+    }
+
+    public function testWillReturnNextChild(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+        $navigationContainer->next();
+
+        $currentPage = $navigationContainer->current();
+        $this->assertInstanceOf(Page::class, $currentPage);
+        $this->assertSame($pages[1]->getOption('opt'), $currentPage->getOption('opt'));
+    }
+
+    public function testWillReturnCurrentKey(): void
+    {
+        $navigationContainer = new NavigationContainer();
+        $this->assertSame(0, $navigationContainer->key());
+    }
+
+    public function testWillReturnInvalidIfNoItems(): void
+    {
+        $navigationContainer = new NavigationContainer();
+        $this->assertFalse($navigationContainer->valid());
+    }
+
+    public function testWillReturnValidWhileIndexExists(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $this->assertTrue($navigationContainer->valid());
+        for ($i = 0; $i < $this->count; $i++) {
+            $this->assertTrue($navigationContainer->valid());
+            $navigationContainer->next();
+        }
+        $this->assertFalse($navigationContainer->valid());
+    }
+
+    public function testWillReturnFalseIfNotHasChildren(): void
+    {
+        $navigationContainer = new NavigationContainer();
+        $this->assertFalse($navigationContainer->hasChildren());
+    }
+
+    public function testWillReturnTrueIfHasChildren(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $this->assertTrue($navigationContainer->hasChildren());
+    }
+
+    public function testWillNotFindOneByNotExistingAttribute(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $result = $navigationContainer->findOneByAttribute('test', 'test');
+        $this->assertNull($result);
+    }
+
+    public function testWillFindOneByExistingAttribute(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $result = $navigationContainer->findOneByAttribute('attr', 'attr #0');
+        $this->assertInstanceOf(Page::class, $result);
+        $this->assertSame($pages[0]->getAttribute('attr'), $result->getAttribute('attr'));
+    }
+
+    public function testWillNotFindManyByNonExistingAttribute(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $results = $navigationContainer->findByAttribute('test', 'test');
+        $this->assertIsArray($results);
+        $this->assertEmpty($results);
+    }
+
+    public function testWillFindManyByExistingAttribute(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $results = $navigationContainer->findByAttribute('attr', 'attr #0');
+        $this->assertIsArray($results);
+        $this->assertInstanceOf(Page::class, $results[0]);
+        $this->assertSame($pages[0]->getAttribute('attr'), $results[0]->getAttribute('attr'));
+    }
+
+    public function testWillNotFindOneByNonExistingOption(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $result = $navigationContainer->findOneByOption('test', 'test');
+        $this->assertNull($result);
+    }
+
+    public function testWillFindOneByExistingOption(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $result = $navigationContainer->findOneByOption('opt', 'opt #0');
+        $this->assertInstanceOf(Page::class, $result);
+        $this->assertSame($pages[0]->getOption('opt'), $result->getOption('opt'));
+    }
+
+    public function testWillNotFindManyByNonExistingOption(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $results = $navigationContainer->findByOption('test', 'test');
+        $this->assertIsArray($results);
+        $this->assertEmpty($results);
+    }
+
+    public function testWillFindManyByExistingOption(): void
+    {
+        $pages = $this->getTestPages();
+
+        $navigationContainer = new NavigationContainer();
+        $navigationContainer->addPages($pages);
+
+        $results = $navigationContainer->findByOption('opt', 'opt #0');
+        $this->assertIsArray($results);
+        $this->assertInstanceOf(Page::class, $results[0]);
+        $this->assertSame($pages[0]->getAttribute('opt'), $results[0]->getAttribute('opt'));
+    }
+
+    /**
+     * @return Page[]
+     */
+    protected function getTestPages(): array
+    {
+        $pages = [];
+
+        for ($i = 0; $i < $this->count; $i++) {
+            $page = new Page();
+            $page->setOption('opt', 'opt #' . $i);
+            $page->setAttribute('attr', 'attr #' . $i);
+            $pages[] = $page;
+        }
+
+        return $pages;
+    }
+}
diff --git a/test/NavigationMiddlewareTest.php b/test/NavigationMiddlewareTest.php
new file mode 100644
index 0000000..2063f7a
--- /dev/null
+++ b/test/NavigationMiddlewareTest.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation;
+
+use Dot\Navigation\NavigationMiddleware;
+use Dot\Navigation\Service\NavigationInterface;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+class NavigationMiddlewareTest extends TestCase
+{
+    /**
+     * @throws Exception
+     */
+    public function testWillCreateMiddleware(): void
+    {
+        $navigation = $this->createMock(NavigationInterface::class);
+
+        $middleware = new NavigationMiddleware($navigation);
+        $this->assertInstanceOf(NavigationMiddleware::class, $middleware);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillProcessRequest(): void
+    {
+        $navigation = $this->createMock(NavigationInterface::class);
+        $request    = $this->createMock(ServerRequestInterface::class);
+        $handler    = $this->createMock(RequestHandlerInterface::class);
+
+        $middleware = new NavigationMiddleware($navigation);
+        $response   = $middleware->process($request, $handler);
+        $this->assertInstanceOf(ResponseInterface::class, $response);
+    }
+}
diff --git a/test/Options/NavigationOptionsTest.php b/test/Options/NavigationOptionsTest.php
new file mode 100644
index 0000000..dfe4f2d
--- /dev/null
+++ b/test/Options/NavigationOptionsTest.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Options;
+
+use Dot\Navigation\Options\NavigationOptions;
+use PHPUnit\Framework\TestCase;
+
+class NavigationOptionsTest extends TestCase
+{
+    public function testWillCreateNavigationOptions(): void
+    {
+        $options = new NavigationOptions();
+        $this->assertInstanceOf(NavigationOptions::class, $options);
+    }
+
+    public function testAccessors(): void
+    {
+        $options = new NavigationOptions();
+        $this->assertIsArray($options->getContainers());
+        $this->assertEmpty($options->getContainers());
+        $options->setContainers(['test']);
+        $this->assertIsArray($options->getContainers());
+        $this->assertCount(1, $options->getContainers());
+        $this->assertTrue($options->getActiveRecursion());
+        $options->setActiveRecursion(false);
+        $this->assertFalse($options->getActiveRecursion());
+    }
+}
diff --git a/test/PageTest.php b/test/PageTest.php
new file mode 100644
index 0000000..cc6601e
--- /dev/null
+++ b/test/PageTest.php
@@ -0,0 +1,85 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation;
+
+use Dot\Navigation\Page;
+use PHPUnit\Framework\TestCase;
+
+class PageTest extends TestCase
+{
+    public function testWillCreatePage(): void
+    {
+        $page = new Page();
+        $this->assertInstanceOf(Page::class, $page);
+    }
+
+    public function testParentAccessors(): void
+    {
+        $page = new Page();
+        $this->assertFalse($page->hasParent());
+        $page->setParent(new Page());
+        $this->assertTrue($page->hasParent());
+        $this->assertInstanceOf(Page::class, $page->getParent());
+    }
+
+    public function testWillAddPage(): void
+    {
+        $page1 = new Page();
+        $page2 = new Page();
+        $this->assertFalse($page1->hasChildren());
+        $this->assertFalse($page2->hasParent());
+        $page1->addPage($page2);
+        $this->assertTrue($page1->hasChildren());
+        $this->assertTrue($page2->hasParent());
+    }
+
+    public function testOptionAccessors(): void
+    {
+        $page = new Page();
+        $this->assertIsArray($page->getOptions());
+        $this->assertEmpty($page->getOptions());
+        $this->assertFalse($page->hasOptions());
+        $page->setOption('opt1', 'value1');
+        $this->assertIsArray($page->getOptions());
+        $this->assertCount(1, $page->getOptions());
+        $this->assertTrue($page->hasOptions());
+        $this->assertSame('value1', $page->getOption('opt1'));
+        $page->setOptions([
+            'opt1' => 'value1',
+            'opt2' => 'value2',
+        ]);
+        $this->assertIsArray($page->getOptions());
+        $this->assertCount(2, $page->getOptions());
+        $this->assertTrue($page->hasOptions());
+    }
+
+    public function testAttributeAccessors(): void
+    {
+        $page = new Page();
+        $this->assertIsArray($page->getAttributes());
+        $this->assertEmpty($page->getAttributes());
+        $this->assertFalse($page->hasAttributes());
+        $page->setAttribute('attr1', 'value1');
+        $this->assertIsArray($page->getAttributes());
+        $this->assertCount(1, $page->getAttributes());
+        $this->assertTrue($page->hasAttributes());
+        $this->assertSame('value1', $page->getAttribute('attr1'));
+        $page->setAttributes([
+            'attr1' => 'value1',
+            'attr2' => 'value2',
+        ]);
+        $this->assertIsArray($page->getAttributes());
+        $this->assertCount(2, $page->getAttributes());
+        $this->assertTrue($page->hasAttributes());
+    }
+
+    public function testGetLabel(): void
+    {
+        $page = new Page();
+        $this->assertSame('Not defined', $page->getLabel());
+        $page->setOption('label', 'Label');
+        $this->assertSame('Label', $page->getLabel());
+    }
+}
diff --git a/test/Provider/ArrayProviderTest.php b/test/Provider/ArrayProviderTest.php
new file mode 100644
index 0000000..329194e
--- /dev/null
+++ b/test/Provider/ArrayProviderTest.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Provider;
+
+use Dot\Navigation\NavigationContainer;
+use Dot\Navigation\Provider\ArrayProvider;
+use PHPUnit\Framework\TestCase;
+
+class ArrayProviderTest extends TestCase
+{
+    public function testWillCreateArrayProvider(): void
+    {
+        $provider = new ArrayProvider();
+        $this->assertInstanceOf(ArrayProvider::class, $provider);
+    }
+
+    public function testAccessors(): void
+    {
+        $provider = new ArrayProvider();
+        $this->assertFalse($provider->hasItems());
+        $provider->setItems(['test']);
+        $this->assertTrue($provider->hasItems());
+        $this->assertSame(['test'], $provider->getItems());
+    }
+
+    public function testGetContainer(): void
+    {
+        $pageSpecs = [
+            [
+                'pages' => [
+                    ['attributes' => ['attr' => 'attribute #0'], 'options' => ['opt' => 'option #0']],
+                    ['attributes' => ['attr' => 'attribute #1'], 'options' => ['opt' => 'option #1']],
+                ],
+            ],
+        ];
+
+        $provider  = new ArrayProvider([
+            'items' => $pageSpecs,
+        ]);
+        $container = $provider->getContainer();
+        $this->assertInstanceOf(NavigationContainer::class, $container);
+        $this->assertCount(2, $container->getChildren());
+    }
+}
diff --git a/test/Provider/FactoryTest.php b/test/Provider/FactoryTest.php
new file mode 100644
index 0000000..1570f7e
--- /dev/null
+++ b/test/Provider/FactoryTest.php
@@ -0,0 +1,128 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Provider;
+
+use Dot\Navigation\Exception\RuntimeException;
+use Dot\Navigation\Provider\ArrayProvider;
+use Dot\Navigation\Provider\Factory;
+use Dot\Navigation\Provider\ProviderInterface;
+use Dot\Navigation\Provider\ProviderPluginManager;
+use Laminas\ServiceManager\Exception\ServiceNotFoundException;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerInterface;
+
+use function sprintf;
+
+class FactoryTest extends TestCase
+{
+    /**
+     * @throws Exception
+     */
+    public function testWillCreateFactoryWithoutProviderPluginManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $factory = new Factory($container);
+        $this->assertInstanceOf(Factory::class, $factory);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillCreateFactoryWithProviderPluginManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+        $manager   = $this->createMock(ProviderPluginManager::class);
+
+        $factory = new Factory($container, $manager);
+        $this->assertInstanceOf(Factory::class, $factory);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFactoryWillNotCreateProviderWithoutProviderType(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessage('Undefined navigation provider type');
+        $factory = new Factory($container);
+        $factory->create([]);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFactoryWillNotCreateProviderWithInvalidProviderType(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $this->expectException(ServiceNotFoundException::class);
+        $this->expectExceptionMessage(
+            sprintf(
+                'A plugin by the name "test" was not found in the plugin manager %s',
+                ProviderPluginManager::class
+            )
+        );
+        $factory = new Factory($container);
+        $factory->create([
+            'type' => 'test',
+        ]);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFactoryWillCreateProviderWithValidProviderTypeAndNoOptions(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $factory  = new Factory($container);
+        $provider = $factory->create([
+            'type' => ArrayProvider::class,
+        ]);
+        $this->assertInstanceOf(ProviderInterface::class, $provider);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFactoryWillCreateProviderWithValidProviderTypeAndOptions(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $factory  = new Factory($container);
+        $provider = $factory->create([
+            'type'    => ArrayProvider::class,
+            'options' => [],
+        ]);
+        $this->assertInstanceOf(ProviderInterface::class, $provider);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFactoryWillGetProviderPluginManagerWithoutInitialProviderPluginManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+
+        $factory = new Factory($container);
+        $this->assertInstanceOf(ProviderPluginManager::class, $factory->getProviderPluginManager());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFactoryWillGetProviderPluginManagerWithInitialProviderPluginManager(): void
+    {
+        $container = $this->createMock(ContainerInterface::class);
+        $manager   = $this->createMock(ProviderPluginManager::class);
+
+        $factory = new Factory($container, $manager);
+        $this->assertInstanceOf(ProviderPluginManager::class, $factory->getProviderPluginManager());
+    }
+}
diff --git a/test/Service/NavigationTest.php b/test/Service/NavigationTest.php
new file mode 100644
index 0000000..01c6574
--- /dev/null
+++ b/test/Service/NavigationTest.php
@@ -0,0 +1,335 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\Service;
+
+use Dot\Authorization\AuthorizationInterface;
+use Dot\Helpers\Route\RouteHelper;
+use Dot\Navigation\Exception\RuntimeException;
+use Dot\Navigation\NavigationContainer;
+use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Page;
+use Dot\Navigation\Provider\ArrayProvider;
+use Dot\Navigation\Provider\FactoryInterface;
+use Dot\Navigation\Service\Navigation;
+use Dot\Navigation\Service\NavigationInterface;
+use Mezzio\Router\RouteResult;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+
+class NavigationTest extends TestCase
+{
+    /**
+     * @throws Exception
+     */
+    public function testNavigationWillInitialize(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertInstanceOf(NavigationInterface::class, $navigation);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testAccessors(): void
+    {
+        $factory     = $this->createMock(FactoryInterface::class);
+        $route       = $this->createMock(RouteHelper::class);
+        $options     = $this->createMock(NavigationOptions::class);
+        $routeResult = $this->createMock(RouteResult::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertNull($navigation->getRouteResult());
+        $navigation->setRouteResult($routeResult);
+        $this->assertInstanceOf(RouteResult::class, $navigation->getRouteResult());
+        $this->assertTrue($navigation->getIsActiveRecursion());
+        $navigation->setIsActiveRecursion(false);
+        $this->assertFalse($navigation->getIsActiveRecursion());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testNavigationWillNotGetInvalidContainer(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessage('Container `test` is not defined');
+        $navigation = new Navigation($factory, $route, $options);
+        $navigation->getContainer('test');
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testNavigationWillGetValidContainer(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+
+        $options = new NavigationOptions([
+            'containers' => [
+                'default' => [
+                    'type' => ArrayProvider::class,
+                ],
+            ],
+        ]);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertInstanceOf(NavigationContainer::class, $navigation->getContainer('default'));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsAllowedWillReturnTrueWithoutAuthorization(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertTrue($navigation->isAllowed(new Page()));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsAllowedWillReturnTrueWithAuthorizationWhenPageHasNoPermission(): void
+    {
+        $factory       = $this->createMock(FactoryInterface::class);
+        $route         = $this->createMock(RouteHelper::class);
+        $options       = $this->createMock(NavigationOptions::class);
+        $authorization = $this->createMock(AuthorizationInterface::class);
+
+        $navigation = new Navigation($factory, $route, $options, $authorization);
+        $this->assertTrue($navigation->isAllowed(new Page()));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsAllowedWillReturnTrueWithAuthorizationWhenPageHasNoRoles(): void
+    {
+        $factory       = $this->createMock(FactoryInterface::class);
+        $route         = $this->createMock(RouteHelper::class);
+        $options       = $this->createMock(NavigationOptions::class);
+        $authorization = $this->createMock(AuthorizationInterface::class);
+
+        $authorization->expects($this->once())->method('isGranted')->willReturn(true);
+
+        $page = new Page();
+        $page->setOption('permission', '');
+        $navigation = new Navigation($factory, $route, $options, $authorization);
+        $this->assertTrue($navigation->isAllowed($page));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsAllowedWillReturnTrueWithAuthorizationWhenPageHasPermissionsAndRoles(): void
+    {
+        $factory       = $this->createMock(FactoryInterface::class);
+        $route         = $this->createMock(RouteHelper::class);
+        $options       = $this->createMock(NavigationOptions::class);
+        $authorization = $this->createMock(AuthorizationInterface::class);
+
+        $authorization->expects($this->once())->method('isGranted')->willReturn(true);
+
+        $page = new Page();
+        $page->setOption('permission', '');
+        $page->setOption('roles', []);
+        $navigation = new Navigation($factory, $route, $options, $authorization);
+        $this->assertTrue($navigation->isAllowed($page));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsActiveWillCacheResults(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertIsArray($navigation->getIsActiveCache());
+        $this->assertEmpty($navigation->getIsActiveCache());
+        $navigation->isActive(new Page());
+        $this->assertIsArray($navigation->getIsActiveCache());
+        $this->assertCount(1, $navigation->getIsActiveCache());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsActiveWillReturnFalseWithoutRouteResult(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertFalse($navigation->isActive(new Page()));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsActiveWillReturnFalseWithoutSuccessfulRouteResult(): void
+    {
+        $factory     = $this->createMock(FactoryInterface::class);
+        $route       = $this->createMock(RouteHelper::class);
+        $options     = $this->createMock(NavigationOptions::class);
+        $routeResult = $this->createMock(RouteResult::class);
+
+        $routeResult->expects($this->once())->method('isSuccess')->willReturn(false);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $navigation->setRouteResult($routeResult);
+        $this->assertFalse($navigation->isActive(new Page()));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsActiveWillReturnFalseWhenPageHasNoRoute(): void
+    {
+        $factory     = $this->createMock(FactoryInterface::class);
+        $route       = $this->createMock(RouteHelper::class);
+        $options     = $this->createMock(NavigationOptions::class);
+        $routeResult = $this->createMock(RouteResult::class);
+
+        $routeResult->expects($this->once())->method('isSuccess')->willReturn(true);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $navigation->setRouteResult($routeResult);
+        $this->assertFalse($navigation->isActive(new Page()));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsActiveWillReturnTrueWhenRequestedRouteMatchesPageRoute(): void
+    {
+        $factory     = $this->createMock(FactoryInterface::class);
+        $route       = $this->createMock(RouteHelper::class);
+        $options     = $this->createMock(NavigationOptions::class);
+        $routeResult = $this->createMock(RouteResult::class);
+
+        $routeResult->expects($this->once())->method('isSuccess')->willReturn(true);
+        $routeResult->expects($this->once())->method('getMatchedRouteName')->willReturn('test');
+
+        $page = new Page();
+        $page->setOption('route', [
+            'route_name' => 'test',
+        ]);
+        $navigation = new Navigation($factory, $route, $options);
+        $navigation->setRouteResult($routeResult);
+        $navigation->setIsActiveRecursion(false);
+        $this->assertTrue($navigation->isActive($page));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testIsActiveWillReturnTrueWhenRequestedRouteMatchesChildPageRoute(): void
+    {
+        $factory     = $this->createMock(FactoryInterface::class);
+        $route       = $this->createMock(RouteHelper::class);
+        $options     = $this->createMock(NavigationOptions::class);
+        $routeResult = $this->createMock(RouteResult::class);
+
+        $routeResult->expects($this->any())->method('isSuccess')->willReturn(true);
+        $routeResult->expects($this->any())->method('getMatchedRouteName')->willReturn('child');
+
+        $childPage = new Page();
+        $childPage->setOption('route', [
+            'route_name' => 'child',
+        ]);
+        $parentPage = new Page();
+        $parentPage->setOption('route', [
+            'route_name' => 'parent',
+        ]);
+        $parentPage->addPage($childPage);
+        $navigation = new Navigation($factory, $route, $options);
+        $navigation->setRouteResult($routeResult);
+        $navigation->setIsActiveRecursion(true);
+        $this->assertTrue($navigation->isActive($parentPage));
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testGetHrefWillCacheResults(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+        $this->assertIsArray($navigation->getHrefCache());
+        $this->assertEmpty($navigation->getHrefCache());
+
+        $page = new Page();
+        $page->setOption('uri', 'page1');
+        $navigation->getHref($page);
+        $this->assertIsArray($navigation->getHrefCache());
+        $this->assertCount(1, $navigation->getHrefCache());
+
+        $page = new Page();
+        $page->setOption('route', [
+            'route_name' => 'page2',
+        ]);
+        $navigation->getHref($page);
+        $this->assertIsArray($navigation->getHrefCache());
+        $this->assertCount(2, $navigation->getHrefCache());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillNotGetHrefForInvalidPage(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessageMatches('/^Unable to assemble href for navigation page.*/');
+        $page = new Page();
+        $navigation->getHref($page);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillGetHrefForValidPage(): void
+    {
+        $factory = $this->createMock(FactoryInterface::class);
+        $route   = $this->createMock(RouteHelper::class);
+        $options = $this->createMock(NavigationOptions::class);
+
+        $navigation = new Navigation($factory, $route, $options);
+
+        $page = new Page();
+        $page->setOption('uri', 'page1');
+        $this->assertSame('page1', $navigation->getHref($page));
+
+        $page = new Page();
+        $page->setOption('route', [
+            'route_name' => 'page2',
+        ]);
+        $this->assertIsString($navigation->getHref($page));
+    }
+}
diff --git a/test/View/NavigationRendererTest.php b/test/View/NavigationRendererTest.php
new file mode 100644
index 0000000..12d25da
--- /dev/null
+++ b/test/View/NavigationRendererTest.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DotTest\Navigation\View;
+
+use Dot\Navigation\NavigationContainer;
+use Dot\Navigation\Options\NavigationOptions;
+use Dot\Navigation\Service\NavigationInterface;
+use Dot\Navigation\View\NavigationRenderer;
+use Mezzio\Template\TemplateRendererInterface;
+use PHPUnit\Framework\MockObject\Exception;
+use PHPUnit\Framework\TestCase;
+
+class NavigationRendererTest extends TestCase
+{
+    /**
+     * @throws Exception
+     */
+    public function testWillCreateNavigationRenderer(): void
+    {
+        $navigation = $this->createMock(NavigationInterface::class);
+        $template   = $this->createMock(TemplateRendererInterface::class);
+        $options    = $this->createMock(NavigationOptions::class);
+
+        $renderer = new NavigationRenderer($navigation, $template, $options);
+        $this->assertInstanceOf(NavigationRenderer::class, $renderer);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillRenderPartial(): void
+    {
+        $navigation = $this->createMock(NavigationInterface::class);
+        $template   = $this->createMock(TemplateRendererInterface::class);
+        $options    = $this->createMock(NavigationOptions::class);
+
+        $renderer = new NavigationRenderer($navigation, $template, $options);
+        $html     = $renderer->renderPartial(new NavigationContainer(), 'partial');
+        $this->assertIsString($html);
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testWillRenderTemplate(): void
+    {
+        $navigation = $this->createMock(NavigationInterface::class);
+        $template   = $this->createMock(TemplateRendererInterface::class);
+        $options    = $this->createMock(NavigationOptions::class);
+
+        $renderer = new NavigationRenderer($navigation, $template, $options);
+        $html     = $renderer->render(new NavigationContainer(), 'template');
+        $this->assertIsString($html);
+    }
+}