In this case, I faced a problem when I want to mock a request with calling third party in test, I had two problem to wait for solving.

  1. Customzing the headers of the request for specific purpose.
  2. Mocking the third party requesting in test.

Here is my thought:

  1. Finding mocked request in laravel TestCase
  2. Based on it and create a customize method of mocking request in test.

Firstly, I found the method of get in TestCase, and it do some operation like:

src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php

/**
 * Visit the given URI with a GET request.
 *
 * @param  string  $uri
 * @param  array  $headers
 * @return \Illuminate\Testing\TestResponse
 */
public function get($uri, array $headers = []) //post, get etc..
{
    // formatting headers data.
    $server = $this->transformHeadersToServerVars($headers);
    // formatting cookies and processing.
    $cookies = $this->prepareCookiesForRequest();

    // The main method for calling and mock test request.
    return $this->call('GET', $uri, [], $cookies, [], $server);
}

Next, we jump into call method and see what is happening.

/**
 * Call the given URI and return the Response.
 *
 * @param  string  $method
 * @param  string  $uri
 * @param  array  $parameters
 * @param  array  $cookies
 * @param  array  $files
 * @param  array  $server
 * @param  string|null  $content
 * @return \Illuminate\Testing\TestResponse
 */
public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
    // create a new request instance.
    $kernel = $this->app->make(HttpKernel::class);

    // if your request is having a file type, extract and process it.
    $files = array_merge($files, $this->extractFilesFromDataArray($parameters));

    // using symfony request to create a request.
    $symfonyRequest = SymfonyRequest::create(
        $this->prepareUrlForRequest($uri), $method, $parameters,
        $cookies, $files, array_replace($this->serverVariables, $server), $content
    );

    // using the kernel to create a response.
    $response = $kernel->handle(
        $request = Request::createFromBase($symfonyRequest)
    );

    // terminating the request.
    $kernel->terminate($request, $response);

    // if having a followRedirects option, follow the redirects.
    if ($this->followRedirects) {
        $response = $this->followRedirects($response);
    }

    // return the response of tap and exception handler.
    return $this->createTestResponse($response);
}

The underlying basic we have done is, let’s digging more about symfony request.

$symfonyRequest = SymfonyRequest::create(
    $this->prepareUrlForRequest($uri), $method, $parameters,
    $cookies, $files, array_replace($this->serverVariables, $server), $content
);

vendor/symfony/http-foundation/Request.php

public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
    // ...

    return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content);
}

private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self
{
    // ... processes some request.
    return new static($query, $request, $attributes, $cookies, $files, $server, $content); // to initalize the request.
}

public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
    $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
}

public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
    // shipping and wrapping the request.
    $this->request = new InputBag($request);
    $this->query = new InputBag($query);
    $this->attributes = new ParameterBag($attributes);
    $this->cookies = new InputBag($cookies);
    $this->files = new FileBag($files);
    $this->server = new ServerBag($server);
    $this->headers = new HeaderBag($this->server->getHeaders()); // here is I need to customize it.

    $this->content = $content;
    $this->languages = null;
    $this->charsets = null;
    $this->encodings = null;
    $this->acceptableContentTypes = null;
    $this->pathInfo = null;
    $this->requestUri = null;
    $this->baseUrl = null;
    $this->basePath = null;
    $this->method = null;
    $this->format = null;
}

vendor/symfony/http-foundation/HeaderBag.php

public function __construct(array $headers = [])
{
    foreach ($headers as $key => $values) {
        $this->set($key, $values);
    }
}

So, we have several strategies to customize the headers.

  1. make a new get or post or etc based on existing method.
public function get($uri, array $headers = [])
{
    $headers = array_replace([
        'this-is-a-customize-header' => 'this-is-a-customize-header-value',
    ], $headers);

    return parent::get($uri, $headers);
}
  1. make a general method to customize the headers.
public function requestWith($method, $uri, array $data = [], array $headers = [])
{
    $headers = array_replace([
        'this-is-a-customize-header' => 'this-is-a-customize-header-value',
    ], $headers);

    return parent::$method($uri, $data, $headers);
}

$response = $this->requestWith('post', '/', [
    'name' => 'Yish'
]);

Also you can use the call method to make a request for you want.

public function requestToThirdParty($uri, array $data = [], array $headers = []) {
    // you can do something here.
    $server = $this->transformHeadersToServerVars($headers);
    $cookies = $this->prepareCookiesForRequest();

    return $this->call('POST', $uri, $data, $cookies, [], $server);
}

Next, I assume you know how to use the call method, but in reality, we have to call third party service in our tests, so how to mock the request with sending a request to third party?

Fortunately, the newer laravel provides Http Tests. https://laravel.com/docs/9.x/http-tests, if you are older version how to do that?


public function testHelloApi()
{
    $client = m::mock('overload:GuzzleHttp\Client');
    $client->shouldReceive('post');
    // do what you want andReturn or etc...

    // make a request
    $this->post('/hello-api', $data);
    // or you need some customize headers
    $this->post('/hello-api', $data, [
        'this-is-a-customize-header' => 'this-is-a-customize-header-value',
    ]);
    // reuseable method
    $this->postWithHello('/hello-api', $data);
}

That’s it, hopefully you enjoy it.