This document describes the testing infrastructure used in the Multisite Language Switcher plugin. The testing system consists of PHPUnit unit tests with WordPress function mocking via Brain\Monkey, Playwright end-to-end browser tests, and integration with GitHub Actions CI/CD. For CI/CD workflow automation and deployment pipelines, see 7.3.
The plugin uses PHPUnit as its primary testing framework for unit and integration tests. The test suite validates plugin functionality without requiring a full WordPress installation by mocking WordPress core functions and objects.
The PHPUnit configuration is defined in phpunit.xml1-32 The bootstrap file tests/phpunit/bootstrap.php initializes the test environment by defining required constants and loading the Composer autoloader.
PHPUnit XML Configuration:
| XML Element | Attribute/Value | Purpose |
|---|---|---|
<phpunit> | bootstrap="tests/phpunit/bootstrap.php" | Test environment initialization |
<testsuites><testsuite> | <directory>tests/phpunit</directory> | Test file location |
<testsuites><testsuite> | Test file prefix/suffix: Test*.php, *Test.php | Test file naming pattern |
<source><include> | <directory suffix=".php">includes/</directory> | Code coverage source directory |
<cache> | <directory>.phpunit.cache</directory> | PHPUnit cache storage |
Constants Defined in Bootstrap:
The bootstrap file tests/phpunit/bootstrap.php8-11 defines these constants for the test environment:
MSLS_PLUGIN_PATH: Set to 'Msls' for plugin path identificationMSLS_PLUGIN__FILE__: Full file path to MultisiteLanguageSwitcher.phpWP_DEBUG: Set to true for debug mode testingHOUR_IN_SECONDS: Set to 3600 for time-based cache testsSources: phpunit.xml1-32 tests/phpunit/bootstrap.php8-11
All test classes extend MslsUnitTestCase tests/phpunit/MslsUnitTestCase.php10-36 which provides standardized setUp() and tearDown() methods along with WordPress function stubs.
MslsUnitTestCase Inheritance and Dependencies
Sources: tests/phpunit/MslsUnitTestCase.php10-36 tests/phpunit/TestMslsMetaBox.php1-17 tests/phpunit/TestMslsSqlCacher.php1-10
The MslsUnitTestCase class tests/phpunit/MslsUnitTestCase.php11-36 implements PHPUnit lifecycle hooks:
| Method | Line References | Operations Performed |
|---|---|---|
setUp() | tests/phpunit/MslsUnitTestCase.php11-24 | parent::setUp(), Brain\Monkey\setUp(), Functions\when('is_plugin_active')->justReturn(false) (WooCommerce stub), WordPress function stubs |
tearDown() | tests/phpunit/MslsUnitTestCase.php26-34 | restore_error_handler(), Mockery::close(), Brain\Monkey\tearDown(), parent::tearDown() |
WordPress Function Stubs in setUp():
The following WordPress functions are automatically stubbed at tests/phpunit/MslsUnitTestCase.php17-22 to return their first argument (passthrough behavior):
Functions\when('__')->returnArg()Functions\when('esc_html__')->returnArg()Functions\when('esc_attr')->returnArg()Functions\when('esc_html')->returnArg()Functions\when('esc_url')->returnArg()Functions\when('sanitize_text_field')->returnArg()Functions\when('wp_kses')->returnArg()Functions\when('wp_kses_post')->returnArg()Functions\when('wp_kses_allowed_html')->returnArg()Sources: tests/phpunit/MslsUnitTestCase.php11-36
Tests are organized to mirror the plugin's includes/ source code structure.
Test Directory Organization
Sources: tests/phpunit/bootstrap.php tests/phpunit/MslsUnitTestCase.php tests/phpunit/TestMslsMetaBox.php tests/phpunit/ContentImport/TestMetaBox.php tests/playwright/testpage.spec.ts playwright.config.ts
| Pattern | Example | Purpose |
|---|---|---|
Test[ClassName].php | TestMslsMetaBox.php | Unit tests for a specific class |
test_<FileRef file-url="https://github.com/lloc/Multisite-Language-Switcher/blob/f04b6033/method_name" undefined file-path="method_name">Hii</FileRef> | test_render_select() | Test method for a specific function |
test_<FileRef file-url="https://github.com/lloc/Multisite-Language-Switcher/blob/f04b6033/scenario" undefined file-path="scenario">Hii</FileRef> | test_render_select_only_one_blog() | Test specific scenario/edge case |
Sources: tests/phpunit/TestMslsMetaBox.php17-382
Brain\Monkey tests/phpunit/MslsUnitTestCase.php5 provides WordPress function mocking via Brain\Monkey\Functions without requiring a WordPress installation.
Brain\Monkey Function Mocking API:
Brain\Monkey Functions API
Example from TestMslsMetaBox::test_suggest():
At tests/phpunit/TestMslsMetaBox.php60-75:
Key Difference:
Functions\expect(): Verifies function is called with specified countFunctions\when(): Stubs function without call count verificationSources: tests/phpunit/MslsUnitTestCase.php5-22 tests/phpunit/TestMslsMetaBox.php60-75
Brain\Monkey provides Brain\Monkey\Actions tests/phpunit/MslsUnitTestCase.php6 for verifying WordPress action/filter hook registrations:
Example from TestMslsMetaBox::test_init() at tests/phpunit/TestMslsMetaBox.php46-48:
This verifies that MslsMetaBox::init() includes/MslsMetaBox.php correctly registers these WordPress action hooks.
Sources: tests/phpunit/MslsUnitTestCase.php6 tests/phpunit/TestMslsMetaBox.php46-48
Plugin classes and WordPress objects are mocked using Mockery tests/phpunit/MslsUnitTestCase.php5
Mockery API for Object Mocking
Example Mock Objects from TestMslsMetaBox::MslsMetaBoxFactory() at tests/phpunit/TestMslsMetaBox.php22-33:
Common Mock Objects:
MslsBlog: Represents a blog in the multisite networkMslsOptions: Plugin settings and configurationMslsBlogCollection: Collection of available blogsWP_Post: WordPress post objectSources: tests/phpunit/MslsUnitTestCase.php5 tests/phpunit/TestMslsMetaBox.php19-35
PHPUnit data providers enable testing multiple scenarios with the same test logic.
Data Provider Execution Flow
Example Data Provider:
Sources: tests/phpunit/TestMslsMetaBox.php120-145
The render_input_provider demonstrates testing multiple scenarios with different configurations:
| Parameters | Expected Behavior | Output Verification |
|---|---|---|
| Existing translation | Show edit link | Contains "Edit the translation" |
| No translation | Show create link | Contains "Create a new translation" |
| Different blog IDs | Proper URL generation | Validates admin_url/edit_post_link calls |
Sources: tests/phpunit/TestMslsMetaBox.php209-244
Many plugin components generate HTML output. Tests use expectOutputString() to validate rendered content.
Output Assertion Flow
Example:
Sources: tests/phpunit/TestMslsMetaBox.php88-94
Complex objects often require factory methods to create properly configured test instances.
Factory Method for Test Instance Creation
Implementation:
Sources: tests/phpunit/TestMslsMetaBox.php19-35
The Content Import system has specialized tests demonstrating integration testing patterns.
Import Testing Sequence
Example Test:
Sources: tests/phpunit/ContentImport/Importers/PostMeta/TestDuplicating.php14-30
Log writer tests validate that import results are properly persisted:
Sources: tests/phpunit/ContentImport/LogWriters/TestAdminNoticeLogger.php18-36
Playwright playwright.config.ts1-27 provides end-to-end browser testing of language switcher functionality in a live WordPress multisite environment.
The configuration file playwright.config.ts1-27 defines test execution parameters:
| Configuration Key | Value | Purpose |
|---|---|---|
testDir | './tests/playwright/' | Test spec file location |
outputDir | './tests/playwright-results/' | Test execution artifacts |
use.baseURL | 'https://msls.co' | WordPress test environment |
reporter | [['list'], ['json', {...}]] | Console + JSON output |
retries | process.env.CI ? 2 : 0 | CI retry strategy for flaky tests |
workers | process.env.CI ? 1 : undefined | Sequential (CI) vs parallel (local) |
projects[0].name | 'chromium' | Browser target |
projects[0].use | devices['Desktop Chrome'] | Viewport and user agent |
CI-Specific Settings:
forbidOnly: !!process.env.CI at playwright.config.ts6: Prevents .only() tests in CIretries: process.env.CI ? 2 : 0 at playwright.config.ts8: Retry flaky tests in CI onlyworkers: process.env.CI ? 1 : undefined at playwright.config.ts9: Sequential execution in CISources: playwright.config.ts1-27
The Playwright test execution pipeline runs through npm scripts and Playwright CLI.
Playwright Test Execution Pipeline
Sources: package.json11 playwright.config.ts1-27 tests/playwright/testpage.spec.ts1-50
The test specification file tests/playwright/testpage.spec.ts1-50 contains three parallel test suites.
Test Organization:
| Test Suite | Line References | Test Type | Selectors/Elements |
|---|---|---|---|
| Widget and Menu | tests/playwright/testpage.spec.ts7-24 | test.describe.parallel() | .widget_mslswidget, .msls-menu |
| Link Position | tests/playwright/testpage.spec.ts26-40 | test.describe.parallel() | .entry-content ul.msls |
| Translation Hint | tests/playwright/testpage.spec.ts42-50 | test() | .widget_mslswidget, getByRole('link') |
1. Widget and Menu Switcher Tests
Parameterized test suite tests/playwright/testpage.spec.ts7-24 verifying language switching in different display contexts:
Widget and Menu Test Execution
2. Link Position Tests
Test suite tests/playwright/testpage.spec.ts26-40 verifying language switchers embedded in post content:
Link Position Test Flow
Tests switchers at three positions: first (nth(0)), second (nth(1)), third (nth(2)) occurrence in .entry-content.
3. Translation Hint Test
Single test tests/playwright/testpage.spec.ts42-50 verifying only_with_translation option:
| Step | Action | Selector | Expected Count |
|---|---|---|---|
| 1 | Click German | getByRole('link', {name: /^Deutsch$/}) | N/A |
| 2 | Count English links | page.locator('.widget_mslswidget').first().getByRole('link', {name: /^English$/}) | 0 (no English translation) |
| 3 | Click English | getByRole('link', {name: /^English$/}) | N/A |
| 4 | Count German links | page.locator('.widget_mslswidget').first().getByRole('link', {name: /^Deutsch$/}) | 0 (no German translation) |
This validates that untranslated languages are hidden when only_with_translation is enabled.
Sources: tests/playwright/testpage.spec.ts1-50
Playwright generates test artifacts configured in playwright.config.ts4-13:
| Configuration | Value | Purpose |
|---|---|---|
outputDir | './tests/playwright-results/' | Screenshots, videos, traces |
reporter[1][1].outputFile | './tests/playwright-report/test-results.json' | Structured JSON results |
.env.local | N/A (gitignored) | Local environment variables |
Artifact Generation Settings:
use.trace: 'on-first-retry': Captures trace only on retry for debuggingGit Ignored Artifacts:
The .gitignore file .gitignore17-19 excludes:
tests/playwright-results/: All test execution artifactstests/playwright/.env.local: Local environment configurationtests/playwright-report/: JSON test results (implicitly ignored)Sources: playwright.config.ts4-16 .gitignore17-19
Prerequisites:
composer installTest Execution Commands:
| Command | Purpose | Requirements |
|---|---|---|
vendor/bin/phpunit | Run all test suites | Default |
vendor/bin/phpunit tests/phpunit/TestMslsMetaBox.php | Run specific test class | Default |
vendor/bin/phpunit --filter test_suggest | Run specific test method | Default |
php -d xdebug.mode=coverage vendor/bin/phpunit --coverage-html tests/coverage/ | Generate HTML coverage report | Xdebug extension |
php -d xdebug.mode=coverage vendor/bin/phpunit --coverage-clover=coverage.xml | Generate Clover XML for CI | Xdebug extension |
Generated Artifacts:
As configured in phpunit.xml15-16 and .gitignore3-5:
| Artifact | Location | Git Ignored | Purpose |
|---|---|---|---|
| PHPUnit cache | .phpunit.cache/ | Yes | Test metadata cache |
| Test results cache | .phpunit.result.cache | Yes | Test execution results |
| HTML coverage | tests/coverage/ | Yes | Visual coverage reports |
| Clover XML | coverage.xml | Yes | CI coverage upload |
Sources: phpunit.xml1-32 .gitignore3-16 .github/workflows/test.yml15-26
Prerequisites:
npx playwright install --with-depshttps://msls.co)tests/playwright/.env.localTest Execution Commands:
| Command | Purpose | Notes |
|---|---|---|
npm run playwright | Install browsers + run tests | Recommended (defined in package.json) |
npx playwright test | Run all test specs | Requires browsers installed |
npx playwright test tests/playwright/testpage.spec.ts | Run specific test file | Single spec execution |
npx playwright test --ui | Interactive UI mode | Debugging with step-through |
npx playwright test --headed | Run with visible browser | Watch test execution |
npx playwright show-report tests/playwright-report/ | View test report | After test execution |
Generated Artifacts:
As configured in playwright.config.ts4-13 and .gitignore17-19:
| Artifact | Location | Git Ignored | Generated When |
|---|---|---|---|
| Test results | tests/playwright-results/ | Yes | Every test run |
| JSON report | tests/playwright-report/test-results.json | Yes | Every test run |
| Screenshots | tests/playwright-results/*.png | Yes | On failure |
| Traces | tests/playwright-results/*.zip | Yes | On retry (first retry only) |
| Environment config | tests/playwright/.env.local | Yes | User-created (optional) |
Sources: package.json11 playwright.config.ts1-27 .gitignore17-19
| Practice | Rationale | Example |
|---|---|---|
Use expectNotToPerformAssertions() | Tests that verify initialization or void methods | MslsMetaBox::init() |
| Use specific assertion methods | Clearer failure messages | assertInstanceOf() vs assertTrue() |
| Test both success and failure paths | Complete coverage | Multiple test methods per feature |
| Use data providers for scenarios | DRY principle | add_data_provider |
Sources: tests/phpunit/TestMslsMetaBox.php50-51 tests/phpunit/TestMslsSqlCacher.php15-18
Mockery and Brain\Monkey verify that expected function calls occur:
Sources: tests/phpunit/TestMslsMetaBox.php60-68
Tests properly manage WordPress global variables:
Sources: tests/phpunit/TestMslsMetaBox.php148-151
PHPUnit is configured to generate coverage reports for the includes/ directory:
Coverage Output Formats:
tests/coverage/ (local development)coverage.xml (CI/CD integration).phpunit.cache.phpunit.result.cacheSources: phpunit.xml26-30 .gitignore3-16
The GitHub Actions workflow .github/workflows/test.yml1-32 runs two sequential jobs on every push.
GitHub Actions Test and Coverage Workflow
Job Definitions:
| Job Name | Trigger Condition | Dependencies | Purpose |
|---|---|---|---|
unit-tests | Every push | None | Run PHPUnit without coverage |
code-coverages | github.ref == 'refs/heads/master' | needs: unit-tests | Generate and upload coverage |
Coverage Job Steps at .github/workflows/test.yml20-31:
actions/checkout@v6: Check out repositorycomposer install: Install PHP dependenciesphp -d xdebug.mode=coverage vendor/bin/phpunit --coverage-clover=coverage.xml: Generate Clover XMLcodecov/codecov-action@v5.5.1: Upload coverage.xml to Codecov (requires CODECOV_TOKEN secret)Sources: .github/workflows/test.yml1-32
For deployment workflows and plugin validation, see 7.3.
| Class | Purpose | Location |
|---|---|---|
MslsUnitTestCase | Base test case with WordPress mocking | tests/phpunit/MslsUnitTestCase.php |
TestMslsMetaBox | Meta box functionality tests | tests/phpunit/TestMslsMetaBox.php |
TestMslsSqlCacher | SQL caching tests | tests/phpunit/TestMslsSqlCacher.php |
TestAdminNoticeLogger | Import logging tests | tests/phpunit/ContentImport/LogWriters/TestAdminNoticeLogger.php |
TestDuplicating | Post meta import tests | tests/phpunit/ContentImport/Importers/PostMeta/TestDuplicating.php |
TestLinking | Attachment linking tests | tests/phpunit/ContentImport/Importers/Attachments/TestLinking.php |
| Utility | Purpose | Usage |
|---|---|---|
Brain\Monkey\Functions | WordPress function mocking | Function expectations and stubs |
Brain\Monkey\Actions | WordPress action/filter mocking | Hook verification |
Mockery::mock() | Object mocking | Create mock objects |
@dataProvider | Parameterized testing | Data-driven tests |
expectOutputString() | Output verification | HTML rendering tests |
Sources: tests/phpunit/MslsUnitTestCase.php5-6 tests/phpunit/TestMslsMetaBox.php5-6
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.