Open-source software for Joomla, Gitea, and web platforms. Home of MokoSuite, MokoGitea, and MokoCLI.
Tennessee
standards/testing
Testing Standards
Testing expectations across all MokoConsulting projects.
PHP (Joomla Extensions)
Framework
PHPUnit 10+ with Joomla's testing framework.
Project Setup
tests/
├── Unit/
│ ├── Engine/
│ │ └── BackupEngineTest.php
│ └── Model/
│ └── ProfilesModelTest.php
├── Integration/
│ └── Api/
│ └── BackupApiTest.php
└── bootstrap.php
Configuration
phpunit.xml in repo root:
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
</phpunit>
Conventions
- Test class name matches source:
BackupEngine→BackupEngineTest - Test methods:
test{MethodName}{Scenario}ortest_{method}_{scenario} - One assertion concept per test method
- Use data providers for multiple input scenarios
- No mocking the database — use real database for integration tests
public function testRunBackupCreatesArchive(): void
{
$engine = new BackupEngine($this->db, $this->tempDir);
$result = $engine->run($this->profile);
$this->assertFileExists($result->archivePath);
$this->assertGreaterThan(0, $result->sizeBytes);
$this->assertSame(BackupStatus::Complete, $result->status);
}
/**
* @dataProvider invalidProfileProvider
*/
public function testRunBackupRejectsInvalidProfile(array $config): void
{
$this->expectException(BackupException::class);
$engine = new BackupEngine($this->db, $this->tempDir);
$engine->run(new Profile($config));
}
Go (MokoGitea)
- Standard
testingpackage withtestify/assert - Table-driven tests for all functions with multiple cases
go test -race ./...must pass- Integration tests tagged with
//go:build integration
func TestParseManifest(t *testing.T) {
tests := []struct {
name string
xml string
want *Manifest
wantErr bool
}{
{"valid joomla", `<manifest><platform>joomla</platform></manifest>`, &Manifest{Platform: "joomla"}, false},
{"empty", "", nil, true},
{"invalid xml", "<broken", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseManifest([]byte(tt.xml))
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want.Platform, got.Platform)
}
})
}
}
TypeScript (MCP Servers)
- Jest or Vitest for unit tests
- Tests in
__tests__/or*.test.tsalongside source - Mock external HTTP calls, not internal logic
- Test tool handlers with sample inputs
describe('backup tool', () => {
it('returns success for valid target', async () => {
const result = await handleBackupRun({ target: 'gitea-db' });
expect(result.content[0].text).toContain('Backup started');
});
it('rejects unknown target', async () => {
await expect(handleBackupRun({ target: 'invalid' }))
.rejects.toThrow('Unknown backup target');
});
});
Coverage Expectations
- New code: 80% line coverage minimum
- Critical paths (security, data integrity): 100% coverage
- Bug fixes: must include regression test
- Coverage is informational, not a gate — meaningful tests over coverage targets
Running Tests
# PHP
make test # or: vendor/bin/phpunit
# Go
go test ./...
# TypeScript
npm test # or: npx jest
All tests run in CI on every PR. Merging requires green CI.
Pages