Giter VIP home page Giter VIP logo

mock-webserver's Introduction

Mock Web Server

Latest Stable Version License ci.yml

Simple, easy to use Mock Web Server for PHP unit testing. Gets along simply with PHPUnit and other unit testing frameworks.

Unit testing HTTP requests can be difficult, especially in cases where injecting a request library is difficult or not ideal. This helps greatly simplify the process.

Mock Web Server creates a local Web Server you can make predefined requests against.

Documentation

See: docs/docs.md

Requirements

  • php: >=7.1
  • ext-sockets: *
  • ext-json: *
  • ralouphie/getallheaders: ~2.0 || ~3.0

Installing

Install the latest version with:

composer require --dev 'donatj/mock-webserver'

Examples

Basic Usage

The following example shows the most basic usage. If you do not define a path, the server will simply bounce a JSON body describing the request back to you.

<?php

use donatj\MockWebServer\MockWebServer;

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

$server = new MockWebServer;
$server->start();

$url = $server->getServerRoot() . '/endpoint?get=foobar';

echo "Requesting: $url\n\n";
echo file_get_contents($url);

Outputs:

Requesting: http://127.0.0.1:61874/endpoint?get=foobar

{
    "_GET": {
        "get": "foobar"
    },
    "_POST": [],
    "_FILES": [],
    "_COOKIE": [],
    "HEADERS": {
        "Host": "127.0.0.1:61874",
        "Connection": "close"
    },
    "METHOD": "GET",
    "INPUT": "",
    "PARSED_INPUT": [],
    "REQUEST_URI": "\/endpoint?get=foobar",
    "PARSED_REQUEST_URI": {
        "path": "\/endpoint",
        "query": "get=foobar"
    }
}

Simple

<?php

use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Response;

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

$server = new MockWebServer;
$server->start();

// We define the server's response to requests of the /definedPath endpoint
$url = $server->setResponseOfPath(
	'/definedPath',
	new Response(
		'This is our http body response',
		[ 'Cache-Control' => 'no-cache' ],
		200
	)
);

echo "Requesting: $url\n\n";

$content = file_get_contents($url);

// $http_response_header is a little known variable magically defined
// in the current scope by file_get_contents with the response headers
echo implode("\n", $http_response_header) . "\n\n";
echo $content . "\n";

Outputs:

Requesting: http://127.0.0.1:61874/definedPath

HTTP/1.1 200 OK
Host: 127.0.0.1:61874
Date: Tue, 31 Aug 2021 19:50:15 GMT
Connection: close
X-Powered-By: PHP/7.3.25
Cache-Control: no-cache
Content-type: text/html; charset=UTF-8

This is our http body response

Change Default Response

<?php

use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Responses\NotFoundResponse;

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

$server = new MockWebServer;
$server->start();

// The default response is donatj\MockWebServer\Responses\DefaultResponse
// which returns an HTTP 200 and a descriptive JSON payload.
//
// Change the default response to donatj\MockWebServer\Responses\NotFoundResponse
// to get a standard 404.
//
// Any other response may be specified as default as well.
$server->setDefaultResponse(new NotFoundResponse);

$content = file_get_contents($server->getServerRoot() . '/PageDoesNotExist', false, stream_context_create([
	'http' => [ 'ignore_errors' => true ], // allow reading 404s
]));

// $http_response_header is a little known variable magically defined
// in the current scope by file_get_contents with the response headers
echo implode("\n", $http_response_header) . "\n\n";
echo $content . "\n";

Outputs:

HTTP/1.1 404 Not Found
Host: 127.0.0.1:61874
Date: Tue, 31 Aug 2021 19:50:15 GMT
Connection: close
X-Powered-By: PHP/7.3.25
Content-type: text/html; charset=UTF-8

VND.DonatStudios.MockWebServer: Resource '/PageDoesNotExist' not found!

PHPUnit

<?php

use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Response;

class ExampleTest extends PHPUnit\Framework\TestCase {

	/** @var MockWebServer */
	protected static $server;

	public static function setUpBeforeClass() : void {
		self::$server = new MockWebServer;
		self::$server->start();
	}

	public function testGetParams() : void {
		$result  = file_get_contents(self::$server->getServerRoot() . '/autoEndpoint?foo=bar');
		$decoded = json_decode($result, true);
		$this->assertSame('bar', $decoded['_GET']['foo']);
	}

	public function testGetSetPath() : void {
		// $url = http://127.0.0.1:61874/definedEndPoint
		$url    = self::$server->setResponseOfPath('/definedEndPoint', new Response('foo bar content'));
		$result = file_get_contents($url);
		$this->assertSame('foo bar content', $result);
	}

	public static function tearDownAfterClass() : void {
		// stopping the web server during tear down allows us to reuse the port for later tests
		self::$server->stop();
	}

}

Delayed Response Usage

By default responses will happen instantly. If you're looking to test timeouts, the DelayedResponse response wrapper may be useful.

<?php

use donatj\MockWebServer\DelayedResponse;
use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Response;

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

$server = new MockWebServer;
$server->start();

$response = new Response(
	'This is our http body response',
	[ 'Cache-Control' => 'no-cache' ],
	200
);

// Wrap the response in a DelayedResponse object, which will delay the response
$delayedResponse = new DelayedResponse(
	$response,
	100000 // sets a delay of 100000 microseconds (.1 seconds) before returning the response
);

$realtimeUrl = $server->setResponseOfPath('/realtime', $response);
$delayedUrl  = $server->setResponseOfPath('/delayed', $delayedResponse);

echo "Requesting: $realtimeUrl\n\n";

// This request will run as quickly as possible
$start = microtime(true);
file_get_contents($realtimeUrl);
echo "Realtime Request took: " . (microtime(true) - $start) . " seconds\n\n";

echo "Requesting: $delayedUrl\n\n";

// The request will take the delayed time + the time it takes to make and transfer the request
$start = microtime(true);
file_get_contents($delayedUrl);
echo "Delayed Request took: " . (microtime(true) - $start) . " seconds\n\n";

Outputs:

Requesting: http://127.0.0.1:61874/realtime

Realtime Request took: 0.015669107437134 seconds

Requesting: http://127.0.0.1:61874/delayed

Delayed Request took: 0.10729098320007 seconds

Multiple Responses from the Same Endpoint

Response Stack

If you need an ordered set of responses, that can be done using the ResponseStack.

<?php

use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Response;
use donatj\MockWebServer\ResponseStack;

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

$server = new MockWebServer;
$server->start();

// We define the servers response to requests of the /definedPath endpoint
$url = $server->setResponseOfPath(
	'/definedPath',
	new ResponseStack(
		new Response("Response One"),
		new Response("Response Two")
	)
);

echo "Requesting: $url\n\n";

$contentOne = file_get_contents($url);
$contentTwo = file_get_contents($url);
// This third request is expected to 404 which will error if errors are not ignored
$contentThree = file_get_contents($url, false, stream_context_create([ 'http' => [ 'ignore_errors' => true ] ]));

// $http_response_header is a little known variable magically defined
// in the current scope by file_get_contents with the response headers
echo $contentOne . "\n";
echo $contentTwo . "\n";
echo $contentThree . "\n";

Outputs:

Requesting: http://127.0.0.1:61874/definedPath

Response One
Response Two
Past the end of the ResponseStack

Response by Method

If you need to vary responses to a single endpoint by method, you can do that using the ResponseByMethod response object.

<?php

use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\Response;
use donatj\MockWebServer\ResponseByMethod;

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

$server = new MockWebServer;
$server->start();

// Create a response for both a POST and GET request to the same URL

$response = new ResponseByMethod([
	ResponseByMethod::METHOD_GET  => new Response("This is our http GET response"),
	ResponseByMethod::METHOD_POST => new Response("This is our http POST response", [], 201),
]);

$url = $server->setResponseOfPath('/foo/bar', $response);

foreach( [ ResponseByMethod::METHOD_GET, ResponseByMethod::METHOD_POST ] as $method ) {
	echo "$method request to $url:\n";

	$context = stream_context_create([ 'http' => [ 'method' => $method ] ]);
	$content = file_get_contents($url, false, $context);

	echo $content . "\n\n";
}

Outputs:

GET request to http://127.0.0.1:61874/foo/bar:
This is our http GET response

POST request to http://127.0.0.1:61874/foo/bar:
This is our http POST response

mock-webserver's People

Contributors

brotkrueml avatar dependabot[bot] avatar donatj avatar dron2004 avatar ellipsis-dev[bot] avatar jmleroux avatar jonasraoni avatar mariadeanton avatar silviopahrig avatar tuutti avatar vitormattos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

mock-webserver's Issues

Php 8.2 upgrade has caused an error

I have been using this library with php version 8.0 for a while.
And when I decided to upgrade the php version to 8.2 my end to end tests are now failing with the following error:

                                                                                                                        
[donatj\MockWebServer\Exceptions\ServerException] Failed to start server. Is something already running on port 5500?  
                                                                                                                        
#1  /var/www/vendor/donatj/mock-webserver/src/MockWebServer.php:85

I tried to debug the code to check if the server was running actually, It was not!

While debugging, I tried to dump $this->isRunning() in File /donatj/mock-webserver/src/MockWebServer.php:

dump($this->isRunning());
for( $i = 0; $i <= 20; $i++ ) {
	usleep(100000);
	$open = @fsockopen($this->host, $this->port);
	if( is_resource($open) ) {
		fclose($open);
		break;
	}
}
dump($this->isRunning());

This was actually returning false on the first dump and true on the second!!

Do you know how is that related to the php upgrade, and how we can solve that?

Fix JsonSerializable deprecation in PHP 8.1

As mentioned (and fixed) in 1fde5ec, the JsonSerializable interface signature has changed in PHP 8.1.

A fix has already been implemented for this several months ago, but it has not been released yet.

Since PHP 8.1 has now been released, it would be really great if a new release can be drafted with PHP 8.1 support.

Please add escapeshellarg around script path

Hello

I get this error in php built-in server:

<br />
<b>Warning</b>:  Unknown: failed to open stream: No such file or directory in <b>Unknown</b> on line <b>0</b><br />
<br />
<b>Fatal error</b>:  Unknown: Failed opening required 'D:\MY' (include_path='D:\pear\pear;C:\xampp\php\PEAR') in <b>Unknown</b> on line <b>0</b><br />

Because the script path have white spaces. For example:

D:\MY PROJECT\symfony\vendor\donatj\mock-webserver\src\..\server\server.php

Guilty line:
vendor\donatj\mock-webserver\src\MockWebServer.php:57
$cmd = "php -S {$this->host}:{$this->port} " . $script;

Please, can you use escapeshellarg or similar around $script variable?

For example:

$cmd = "php -S {$this->host}:{$this->port} " . escapeshellarg($script);

Thanks a lot

Failed to start server. In GitHub Actions

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

Mocking a remote service in an e2e test

I have been asked to create an end to end test which covers an endpoint. This endpoint is a proxy for another web service.

I am trying to use this package to mock the remote web service responses, so that I can create a test for the various http codes the service it calls can return.

For some reason, when I set a response for a path, and then call my services endpoint I do not get any body back to assert against.

My test setUp creates an instance of the server, running on the same port as my remote service, localhost:5100 so I was expecting, perhaps incorrectly, that when my e2e test called the endpoint url, the code there would try and get a response from the remote service and instead hit the mock web server on localhost:5100 instead, and then return an instance of donatj\MockWebServer\Response instead of GuzzleHttp\Psr7\Response as that is what I configured on my $this->mockServer->setResponseOfPath().

I am really struggling to understand why I can build a stub response, set it to the path, but when my code under test calls the url, the returned response is a Guzzle one with no body.

Lessen startup time

Right now there is a sleep(1) waiting for the server to start as it's forked and we need to delay a little.

We should either hit the server or something else to see if it's running rather than having a hardcoded sleep. My gut says we'd be able to start up a fair deal more quickly.

Server start blocks

This was just the library I was looking for to test out a HTTP client layer I was writing for an application. Enabled sockets extension in php.ini (running PHP version 7.2.1 on Windows), and added mock-webserver with composer as indicated.

I was following the PHPUnit example, wrote the following "test" to check out the server:

<?php
/**
 * Created by PhpStorm.
 * User: lajtha
 * Date: 2018-02-10
 * Time: 07:34
 */

namespace Test\Client\HTTP;

use donatj\MockWebServer\MockWebServer;
use PHPUnit\Framework\TestCase;

class HTTPClientImplTest extends TestCase
{
    protected static $server;

    public static function setUpBeforeClass()
    {
        print ("Starting server.\n");
        self::$server = new MockWebServer(8084);
        self::$server->start();
        print ("Server started.\n");
    }

    public function testSimpleGet()
    {
        print ("Waiting a bit. \n");
        sleep(120);
        print ("No more waiting.\n");
    }

    public static function tearDownAfterClass()/* The :void return type declaration that should be here would cause a BC issue */
    {
        print ("Server stopping.\n");
        self::$server->stop();
        print ("Server stopped.\n");
    }
}

I expect the output to be:

Starting server.
Server started.
Waiting a bit.
No more waiting.
Server stopping.
Server stopped.

But just get:
Starting server.

The server starts up, and is operational, but doesn't run the tests. Tried from the command line and from PhpStorms too. I don't have much experience with PHP, tried to add some threading but found that it's not as easy as in Java. How could I make the test work?
Thanks, Balázs

Unable to cause Exceptions in a ResponseStack

I ran into a situation at work where I wanted to write a test around retrying an API if there are connection issues (specifically a GuzzleHttp ConnectException). The application code would try to make a call to the API 3 times before giving up.

What I ended up trying was:

class TimeoutResponse extends Response
{
	public function getBody(RequestInfo $request)
	{
		throw new ConnectException(...);
	}
}

and then

$this->mockServer->setResponseOfPath('/v1/get-data',
    new ResponseStack(
        new TimeoutResponse('timeoutBody'),
        new TimeoutResponse('timeoutBody'),
        new Response('goodBody')
    )
);

I was never getting the 'goodBody' response back and I narrowed it down to how ResponseStack is handling ->next().

InternalServer.php::sendResponse:

echo $response->getBody($this->request);

if( $response instanceof MultiResponseInterface ) {
    $response->next();
    self::storeResponse($this->tmpPath, $response);
}

When the exception is thrown, the $response->next() is never called.

I believe a valid fix for this would be (in PHP5.4 since we can't use finally):

try {
    echo $response->getBody($this->request);
    if( $response instanceof MultiResponseInterface ) {
        $response->next();
        self::storeResponse($this->tmpPath, $response);
    }
} catch (Exception $exception) {
    if( $response instanceof MultiResponseInterface ) {
        $response->next();
        self::storeResponse($this->tmpPath, $response);
    }
    throw $exception;
}

I made the changes and tried to write an integration test, but was having trouble accessing my TimeoutResponse and TimeoutException test classes not being recognized because of namespace issues. Before mucking around with it more, I was going to see if this seems like a reasonable feature and/or approach?

Simulate slow responses

It would be great it there would be a way to simulate slow responses (IE: by manually adding a sleep()) call to it.

As it stands right now, it does not look like there is any way to do this. Is this something that could be considered?

Base path for routes

Is it possible to support a base path for routes? This can then be mapped to a base file path for wild card routes

thanks

Convert index script output to JSON

If you did output request details

Requesting: http://127.0.0.1:8123/endpoint?get=foobar

{
    "_GET": {
        "get": "foobar"
    },

as JSON we then would be able to run assertions over it. E.g.

<?php
$rsp = $this->client->request("GET", $url, [ "headers" => ["X-Foo" => "..."] ]);
$body = \json_decode((string)$rsp->getBody());
$this->assertEquals("...", $body->HEADERS->X-Foo);

Feature: Mock a dynamic url

What if we could mock an endpoint like this /endpoint/product/*
So then it would not matter what parameter we send in the * we would get the same response.

<?php

use donatj\MockWebServer\MockWebServer;
use donatj\MockWebServer\RequestInfo;
use donatj\MockWebServer\Response;

require 'vendor/autoload.php';

$server = new MockWebServer();
$server->start();

$url     = $server->setResponseOfPath("/api/endpoint/product/*", new Response('abc'));
 //request '/api/endpoint/product/1' will result in: abc

Server gets freezed when debugging

Hi, I'm looking forward to been able to test with mock-webserver.

When I debug (I use xDebug) an script that has a request to the MockWebServer, I don't get any response, and the script gets stuck. My hypothesis is that when debugging, the new php process for the server also becomes debugged, and stays waiting for continuing.

The same script works as expected when I'm not debugging. I'm using the scripts sampled in the documentation.

Is there any way to make it debuggable?

Thanks!

setResponseOfPath not working with query parameters

This works:

$url = $this->server->setResponseOfPath("/api/endpoint", new Response('abc' ));
$content = file_get_contents($url);
print_r($content);

While this does not:

$url = $this->server->setResponseOfPath("/api/endpoint?with=parameter", new Response('def'));
$content = file_get_contents($url);
print_r($content);

Instead of returning def, it returns the information about the request.

Problem with child paths not working

I'm trying to use the url in this following way:

POST /api/properties/XXXXXXXX
PUT /api/properties/XXXXXXXX/sellers

While doing this, the first url works but the second one gives me:

Client error: `PUT http://api-cli:8086/api/properties/XXXXXX/sellers` resulted in a `404 Not Found` response:
VND.DonatStudios.MockWebServer: Resource '/api/propiedades/XXXXXXX/sellers' not found!

Following the examples on the documentation I'm using:

$server = new MockWebServer(8086);
$server->setDefaultResponse(new NotFoundResponse);
$response = new ResponseByMethod([
            ResponseByMethod::METHOD_POST => new Response(
                '{ "id": ".$id." }',
                ['Cache-Control' => 'no-cache', 'Content-Type' => 'application/json'],
                201
            ),
            ResponseByMethod::METHOD_PUT => new Response(
                    '{ "id": '.$id_mu.' }',
                    ['Cache-Control' => 'no-cache', 'Content-Type' => 'application/json'],
                    201
                ),
        ]);
$server->setResponseOfPath(
            '/api/properties/'.$id.'/sellers',
            $response
);
 $server->setResponseOfPath(
            '/api/properties/'.$id,
            $response
);

Feature: allow to have the server file descriptors or logs available

I'm using this to mock server responses of an API using codeception helper. This is working as expected but I'm looking to have the debug sent into the log file (https://github.com/donatj/mock-webserver/blob/master/src/MockWebServer.php#L77) public so we can ready the output of the process and have it as debug output in the process.

The intent is to be able to showcase that the request was received and the response sent when running the tests in debug mode.

Also is an option to have a callable on the Response() object so we can output on the excecution insted of the previous idea.

setResponseOfPath body content

Hello,
first of all thanks for the project, it is being very useful to me.

But I want to validate that what my controller sends to the mock is correct, I am using setResponseOfPath. There is a way to recover a body like the basic one, but by customizing the responses.

{
    "_GET": {
        "get": "foobar"
    },
    "_POST": [],
    "_FILES": [],
    "_COOKIE": [],
    "HEADERS": {
        "Host": "127.0.0.1:61355",
        "Connection": "close"
    },
    "METHOD": "GET",
    "INPUT": "",
    "PARSED_INPUT": [],
    "REQUEST_URI": "\/endpoint?get=foobar",
    "PARSED_REQUEST_URI": {
        "path": "\/endpoint",
        "query": "get=foobar"
    }
}

Thanks for your time.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.