First: Mock the Actual Controller to return Exception
namespace App\Tests\Integration\Sentry\Mock; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; class UnauthorizedHttpExceptionController { public function index(): JsonResponse { throw new UnauthorizedHttpException('Unauthorized Http Exception!'); } }
Then, Actual Integration Test
namespace App\Tests\Integration\Sentry; use App\Controller\PingController; use App\Entity\UserDB\User; use App\Tests\Integration\Controller\ControllerIntegrationTestCase; use App\Tests\Integration\Sentry\Mock\AccessDeniedExceptionController; use App\Tests\Integration\Sentry\Mock\DruidExceptionController; use App\Tests\Integration\Sentry\Mock\NotFoundHttpExceptionController; use App\Tests\Integration\Sentry\Mock\TooManyRequestsHttpExceptionController; use App\Tests\Integration\Sentry\Mock\UnauthorizedHttpExceptionController; use App\Tests\Integration\Sentry\Mock\UnknownUserExceptionController; use Generator; use Psr\Log\LoggerInterface; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; class LoggerExceptionTest extends ControllerIntegrationTestCase { /** * @var MockResponse */ private $mockResponse; public function setUp(): void { parent::setUp(); $this->mockResponse = new MockResponse(); } public function testOnDruidExceptionSendLogTagIsFoundOnSentryRequest(): void { $this->mockHttpClient(); self::$container->set(PingController::class, new DruidExceptionController()); $this->jsonRequest('GET', '/v3/ping'); $result = json_decode($this->mockResponse->getRequestOptions()['body']); self::assertObjectHasAttribute('tags', $result); self::assertObjectHasAttribute('log', $result->tags); self::assertObjectHasAttribute('system', $result->tags); self::assertSame('sentry', $result->tags->log); self::assertSame('druid', $result->tags->system); } /** * @dataProvider sentryIgnoreErrorExceptionsDataProvider */ public function testSentryIgnoreErrorsExceptionDoesNotSendRequestToSentry($sentryIgnoreErrorException): void { $this->mockHttpClient(); self::$container->set(PingController::class, $sentryIgnoreErrorException); $this->jsonRequest('GET', '/v3/ping'); $requestBody = $this->mockResponse->getRequestOptions(); self::assertEmpty($requestBody); } /** * @dataProvider logExceptionEqualOrHigherThanErrorDataProvider */ public function testOnLogExceptionEqualOrHigherThanErrorExceptionSentryLogTagIsFoundOnSentryRequest(string $exceptionType): void { $this->mockHttpClient(); $logger = self::$container->get(LoggerInterface::class); $logger->{$exceptionType}('Some exception Message'); $requestBody = $this->mockResponse->getRequestOptions()['body']; $result = json_decode($requestBody); self::assertObjectHasAttribute('tags', $result); self::assertObjectHasAttribute('log', $result->tags); self::assertSame('sentry', $result->tags->log); } /** * @dataProvider logExceptionLowerThanErrorDataProvider */ public function testOnLogExceptionLowerThanErrorExceptionNoSentryRequestIsSent(string $exceptionType): void { $this->mockHttpClient(); $logger = self::$container->get(LoggerInterface::class); $logger->{$exceptionType}('Some Exception Message'); $requestBody = $this->mockResponse->getRequestOptions(); self::assertEmpty($requestBody); } public function testOnLogErrorExceptionUserInformationIsFound(): void { $this->setIpAddressOnRequest(); $this->setTokenStorage(); $this->mockHttpClient(); $logger = self::$container->get(LoggerInterface::class); $logger->error('Some Error Message'); $requestBody = $this->mockResponse->getRequestOptions()['body']; $result = json_decode($requestBody); self::assertObjectHasAttribute('user', $result); self::assertObjectHasAttribute('id', $result->user); self::assertSame('123', $result->user->id); self::assertSame('127.0.0.1', $result->user->ip_address); } public function testOnLogErrorExceptionExtraDataIsFoundIfRequestIdIsSetOnRequest(): void { $this->setRequestIdOnRequest(); $this->mockHttpClient(); $logger = self::$container->get(LoggerInterface::class); $logger->error('Some Error Message'); $requestBody = $this->mockResponse->getRequestOptions()['body']; $result = json_decode($requestBody); self::assertObjectHasAttribute('extra', $result); self::assertSame('234', $result->extra->request_id); } public function logExceptionEqualOrHigherThanErrorDataProvider(): Generator { yield ['error']; yield ['critical']; yield ['alert']; yield ['emergency']; } public function logExceptionLowerThanErrorDataProvider(): Generator { yield ['warning']; yield ['notice']; yield ['info']; yield ['debug']; } public function sentryIgnoreErrorExceptionsDataProvider(): Generator { yield [new UnknownUserExceptionController()]; yield [new AccessDeniedExceptionController()]; yield [new UnauthorizedHttpExceptionController()]; yield [new TooManyRequestsHttpExceptionController()]; yield [new NotFoundHttpExceptionController()]; } private function setTokenStorage(): void { $user = new User(); $user->setId('123'); $token = new UsernamePasswordToken( $user, null, 'secure_area', ['ROLE_ADMIN'] ); static::$kernel ->getContainer() ->get('security.token_storage') ->setToken($token); } private function setIpAddressOnRequest(): void { $requestStack = self::$container->get('request_stack'); $requestStack->push( new Request( [], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1'] ) ); } private function setRequestIdOnRequest(): void { $requestStack = self::$container->get('request_stack'); $requestStack->push( new Request( [], [], [], [], [], ['REQUEST_ID' => '234'] ) ); } private function mockHttpClient(): void { $mockClient = new MockHttpClient($this->mockResponse); $psr18client = new Psr18Client($mockClient); $roundRobinClient = self::$container->get('http.async.test.client'); $roundRobinClient->addHttpClient($psr18client); } }
Also, need to configure services_yaml on test to configure mock http dependencies
services: _defaults: autowire: true autoconfigure: true public: true App\Security\UserProvider: public: true Symfony\Component\Security\Core\Security: public: true # App\EventListener\ExceptionListener: tags: - { name: kernel.event_listener, event: kernel.exception } security.authorization_checker: class: Symfony\Component\Security\Core\Authorization\AuthorizationChecker public: true App\Service\AnalyticsService: public: true ph.rate_limiting.test.calculation.analytics.api: alias: ph.rate_limiting.calculation.analytics.api public: true ph.rate_limiting.test.calculation.souq.analytics.api: alias: ph.rate_limiting.calculation.souq.analytics.api public: true # Public service definitions - required in order to replace services in tests # If the service identifier is an interface, you'll have to manually map it back to # its implementation App\Repository\UserDB\AnalyticsConsoleUserStateRepository: App\Repository\UserDB\CampaignRepository: App\Repository\UserDB\CampaignTermsRepository: App\Repository\UserDB\CampaignTermsAcknowledgementRepository: App\Repository\UserDB\CommissionGroupRepositoryInterface: class: App\Repository\UserDB\CommissionGroupRepository App\Repository\UserDB\CreativeTagRepository: App\Repository\UserDB\NetworkRepositoryInterface: class: App\Repository\UserDB\NetworkRepository App\Repository\UserDB\NetworkTermsRepositoryInterface: class: App\Repository\UserDB\NetworkTermsRepository App\Repository\UserDB\PartnerRepository: App\Repository\UserDB\PartnerGroupRepository: App\Repository\UserDB\ParticipationRepository: App\Repository\UserDB\UserRepository: App\Repository\UserDB\NetworkRepository: App\Repository\UserDB\FeatureRepositoryInterface: class: App\Repository\UserDB\FeatureRepository App\Repository\UserDB\UserResourceActionRepositoryInterface: class: App\Repository\UserDB\UserResourceActionRepository App\Service\PartnerizeTagService: App\Service\NetworkService: App\Service\FeatureService: App\Service\PartnerProspects\ProspectInvitationService: arguments: { $gatewaySourceName: 'my_test_gateway' } App\Service\Terms\NetworkTermsService: PH\UarBundle\Service\UserService: App\Repository\InputDB\AutogeneratedInvoiceRepositoryInterface: App\Service\AutogeneratedInvoiceReportService: PH\RateLimitingBundle\Services\RateLimitingServiceManager: psr18.druid: class: App\Tests\Integration\Mocks\PsrHttpClientProxyMock http.async.test.client: class: Http\Client\Common\HttpClientPool\RoundRobinClientPool Sentry\Transport\TransportFactoryInterface: class: Sentry\SentryBundle\Transport\TransportFactory arguments: $httpClient: '@http.async.test.client' $logger: null