I will share something related to unit testing here with everyone who's interested in mocking
$this->app()->http()->client()
in a different way:
I have this line in my addon code, and I am going to mock the response as opposed to sending the HTTP request to server (api.ipstack.com):
$res = $client->get("
http://api.ipstack.com/$earliest_ip?access_key=$api");
Now, we have to mock the response to something like
PHP:
json_encode([
'ip' => '172.24.12.100',
'country_name'=>'US'
])
Here is how I did it:
Step 1:
Create a Code event listener:
navigate: ACP -> Development-> Code event listeners.
Listening to event: http_client_options
Event hint: blank
Execute callback: Earl\AddOnName\tests\Unit\Listener :: httpClientOptions_mock_response
then create this class
Earl\AddOnName\tests\Unit\Listener
, and method
httpClientOptions_mock_response
Select your add-on, put a description, Save.
Now remember, You must disable this code event listener before you execute the build-addon command, or this code event listener will work every time XenForo tries to make requests using the $client object. You do not want that on your production site. (I asked a question
here about how to enable code event listeners on the fly by keeping them disabled in default, but no one responded, so this is how we do it)
Step 2:
Listener class:
PHP:
namespace Earl\AddOnName\tests\Unit;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\Create;
class Listener
{
public static function httpClientOptions_mock_url(array &$options) :void
{
if (\XF::config('development')['enabled']) {
$originalHandler = new CurlHandler();
$customHandler = function (RequestInterface $request, array $options) use ($originalHandler): PromiseInterface {
$uri = (string) $request->getUri();
if (strpos($uri, "http://api.ipstack.com/") === 0) {
return Create::promiseFor(new Response(200, [], \XF\Util\Json::encodePossibleObject([
'ip' => '172.24.12.100',
'country_name'=>'US'
])));
}
return $originalHandler($request, $options);
};
$stack = HandlerStack::create($customHandler);
$options['handler'] = $stack;
}
}
}
This way we use GuzzleHttp's middleware to intercept the HTTP connection and short-circuit it, then return the mocked response as opposed to sending the real request to the server,
We also check if we are sending the request to our specific URL that we need to mock, otherwise we will return the original response without mocking. In this way, we can control which URL we are mocking, and check what exactly we are mocking, with our custom guzzle HTTP handler, we have the $response object so we have full control of it.
If you need more explanation on how this works, copy this listener class PHP code and paste it into Chatgpt
that's it!
Now you can write In your test files, your assets. Happy mocking