---
title: "Functional testing"
manual: "LLM Foundation"
version: "0.41"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:testing-functional-testing@0.41"
source: "Testing/FunctionalTesting.rst"
rendered: "2026-10-20T08:48:47+00:00"
---

# Functional testing {#testing-functional-testing}

## Running functional tests {#testing-functional}

**Run functional tests**

```bash
# Run TYPO3 functional tests
Build/Scripts/runTests.sh -s functional

# Alternative: Via Composer script
composer ci:test:php:functional
```

With the database configured, unexpected TYPO3 initialization failures and
teardown integrity failures fail the suite. The shared
`Tests/Functional/AbstractFunctionalTestCase.php` skips only when its
database-configuration check finds no configuration; a failed bootstrap is
not a successful test run. Parent teardown runs after setup completes and its
failures remain visible.

The Unit suite checks this behavior using bounded child-PHPUnit executions:
an invalid package fails initialization, a leaked error-reporting setting
fails teardown, a clean lifecycle executes its assertion, and absent database
configuration produces the deliberate skip. The deliberately failing child
fixtures live outside the normal Functional discovery directory. Each child
uses an owned instance identifier through the testing framework's protected
instance seam. Concurrent success and failed-bootstrap children are checked
together; the parent stops any unfinished child and removes only its owned
instance directories, including after a child fails.

## Functional test example {#testing-functional-example}

**Example: Functional test**

```php
<?php

namespace Netresearch\NrLlm\Tests\Functional\Repository;

use Netresearch\NrLlm\Domain\Model\Provider;
use Netresearch\NrLlm\Domain\Repository\ProviderRepository;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class ProviderRepositoryTest extends FunctionalTestCase
{
    protected array $testExtensionsToLoad = [
        'netresearch/nr-vault',
        'netresearch/nr-llm',
    ];

    private ProviderRepository $repository;

    protected function setUp(): void
    {
        parent::setUp();
        $this->repository = $this->get(ProviderRepository::class);
    }

    public function testFindOneByIdentifierReturnsProvider(): void
    {
        $this->importCSVDataSet(__DIR__ . '/Fixtures/providers.csv');

        $provider = $this->repository->findOneByIdentifier('openai-test');

        $this->assertInstanceOf(Provider::class, $provider);
        $this->assertEquals('OpenAI Test', $provider->getName());
    }
}
```

## Test fixtures {#testing-fixtures}

### CSV fixtures {#testing-csv-fixtures}

**Tests/Functional/Fixtures/providers.csv**

```text
"tx_nrllm_provider"
"uid","pid","identifier","name","adapter_type","is_active"
1,0,"openai-test","OpenAI Test","openai",1
```

### JSON response fixtures {#testing-json-fixtures}

**Tests/Fixtures/openai_chat_response.json**

```json
{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-5",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Test response"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 5,
    "total_tokens": 15
  }
}
```

## Mutation testing {#testing-mutation}

The extension uses Infection for mutation testing to ensure test quality.

### Running mutation tests {#testing-mutation-running}

**Run mutation tests**

```bash
# Run mutation tests via runTests.sh
Build/Scripts/runTests.sh -s mutation

# Alternative: Via Composer script
composer ci:test:php:mutation
```

### Interpreting results {#testing-mutation-results}

-   **MSI (Mutation Score Indicator)**: Percentage of mutations killed.
-   **Target**: >60% MSI indicates good test quality.
-   **Current**: 58% MSI (459 tests).

**Mutation testing results**

```text
Mutation Score Indicator (MSI): 58%
Mutation Code Coverage: 85%
Covered Code MSI: 68%
```

## Best practices {#testing-best-practices}

1.  **Isolate tests**: Each test should be independent.
1.  **Mock external APIs**: Never call real APIs in unit tests.
1.  **Use data providers**: For testing multiple scenarios.
1.  **Test edge cases**: Empty inputs, null values, boundaries.
1.  **Descriptive names**: Test method names should describe behavior.
1.  **Arrange-Act-Assert**: Follow AAA pattern.
1.  **Fast tests**: Unit tests should complete in milliseconds.
1.  **Coverage goals**: Aim for >80% line coverage.
