donatj / mock-webserver

Simple mock web server in PHP for unit testing.
MIT License
131 stars 21 forks source link

Failed to start server. In GitHub Actions #52

Closed Tygovanommen closed 1 year ago

Tygovanommen commented 1 year ago

Writing and executing unit tests locally works fine with this library, but when executing it through Github actions it gives me the following error: donatj\MockWebServer\Exceptions\ServerException: Failed to start server. Is something already running on port 8080?

I've tried multiple ports and even the dynamic port, but it does not work. Below the code I add to my unit test.

abstract class ApiTestCase extends TestCase {
    protected MockWebServer $server;

    protected function setUp(): void {
        $this->server = new MockWebServer();
        $this->server->start();

        $this->server->setResponseOfPath(
            '/' . AuthenticationApi::TOKEN_URI,
            new ResponseStack(
                new Response( $this->getAuthenticatedJson() )
            )
        );
    }
}

And my workflow yml file:

name: PHPUnit tests

on:
  push:
    branches:
      - development

jobs:
  run-tests:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.0
          extensions: sockets json

      - name: Install dependencies
        run: |
          composer install --no-interaction --no-ansi

      - name: Run PHPUnit tests
        run: |
          ./vendor/bin/phpunit tests --coverage-text
donatj commented 1 year ago

Judging by your example, I am guessing you are not tearing down the server?

You need to have your test stop the server before creating a new one or you will spawn multiple concurrent servers.

Starting a MockWebServer actually spawns a new process that does not get automatically garbage collected, so you have to actually stop the old server.

setUp is run before each test case. If you're not shutting it down after each test case, either the port is going to already be taken or you're going to be spinning up a TON of servers.

Something as simple this would fix it:

    protected function tearDown() : void {
        $this->server->stop();
    }

While the simple tearDown() should work, it's potentially really slow. Setting up and tearing down the server takes actual time. Doing that over and over can take significant time.

If you look at the phpunit example this is why the server is setup once for the entire TestCase class rather than for every individual test.

Tygovanommen commented 1 year ago

@donatj thanks for the quick response.

My test class already contains the tearDown like you're suggesting. The full class looks like this:

use Akeneo\Pim\ApiClient\Api\AuthenticationApi;
use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Response;
use donatj\MockWebServer\ResponseStack;
use PHPUnit\Framework\TestCase;

abstract class ApiTestCase extends TestCase {
    protected MockWebServer $server;

    /**
     * {@inheritdoc}
     */
    protected function setUp(): void {
        $this->server = new MockWebServer();
        $this->server->start();

        $this->server->setResponseOfPath(
            '/' . AuthenticationApi::TOKEN_URI,
            new ResponseStack(
                new Response( $this->getAuthenticatedJson() )
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function tearDown(): void {
        $this->server->stop();
    }

    private function getAuthenticatedJson(): string {
        return <<<JSON
            {
                "refresh_token" : "this-is-a-refresh-token",
                "access_token" : "this-is-an-access-token"
            }
JSON;
    }
}

This class I then extend inside my actual testing classes:

use connector\Connector;
use ReflectionException;

class ConnectorTest extends ApiTestCase {

    /** @test */
    public function authorize(): void {
        // Arrange
        $akeneo_connector = new Connector( $this->server->getServerRoot() );

        // Act
        $akeneo_connector->authorize();

        // Assert
        $this->assertTrue( true );
    }

}

Running it locally works as expected. Just the GitHub actions won't work.

donatj commented 1 year ago

Ok, I have created a test project

https://github.com/donatj/tmp-action-test

The action seems to run as expected for me.

I've sent you an invite if there is anything you would like to add to the repository to help indicate the issue.

https://github.com/donatj/tmp-action-test/invitations

Tygovanommen commented 1 year ago

@donatj thank you very much for providing me with this repo. I was able to rebuild my test through yours, and figured out what is causing this issue.

I am using the WP_Mock library in other tests and using a bootstrap file to autoload and bootstrap wp_mock.

My bootstrap file looks like:

<?php

require_once __DIR__ . '/../vendor/autoload.php';

WP_Mock::setUsePatchwork( true );
WP_Mock::bootstrap();

Having WP_Mock::setUsePatchwork( true ); is causing the issue. I removed it (because I do not need it) and the problem was resolved!