Any way to to use PHPUnit for testing API requests and responses using just PHP? - phpunit

The responses are in JSON and I am using a custom-built MVC framework which I'm not sure how the request and response process is produced. Service methods are created using the following syntax.
public function getSessionsMethod()
{
// data auto encoded as JSON
return array('hello', 'world');
}
A request from JavaScript would look like this /svc/api/getSessions.
My initial thought was to simply use a streams approach are there best practices for this form of testing?
public function testCanGetSessionsForAGivenId()
{
$params = http_build_query(
array(
'id' => 3,
)
);
$options = array(
'http' => array(
'method' => 'GET',
'content' => $params,
)
);
$context = stream_context_create($options);
$response = file_get_contents(
'http://vbates/svc/api/getSessions', false, $context
);
$json = json_decode($response);
$this->assertEquals(3, $json->response);
}

This doesn't look like unit testing to me but rather integration testing. You can use PHPUnit to do it, but you should understand the difference first.
There are many components involved in getting the response for a given service method:
The dispatcher: Extracts the parameters from the URL and dispatches to the appropriate service method.
The service method: Does the real work to be tested here.
The JSON encoder: Turns the service method's return value into a JSON response.
You should first test these individually in isolation. Once you've verified that the dispatcher and encoder work for general URLs and return values, there's no point in wasting cycles testing that they work with every service method.
Instead, focus your effort on testing each service method without involving these other components. Your test case should instantiate and call the service methods directly with various inputs and make assertions on their return values. Not only will this require less effort on your part, it will make tracking down problems easier because each failure will be limited to a single component.

Related

MassTransit and WebApplicationFactory functional testing

I'm making use of MassTransit to receive some messages from a client's application, and redistribute the message within our environment with some routing headers added.
Whilst we are processing a high amount of messages, not all consuming applications are going to be interested in the whole set of the messages. As such the various consumers are configured with the SNS/SQS FilterPolicy attribute to ensure that only the messages we care about are consumed.
The issues I'm experiencing comes about when using the WebApplicationFactory and the IServiceCollection AddMassTransitTestHarness extension method.
The first issue might be down to my misunderstanding. As I'm using SQS/SNS specific functionality, the InMemoryTestHarness is unsuitable. I'm calling the below snippet when configuring my MassTransitTestHarness:
services.AddMassTransitTestHarness( x =>
{
RegisterConsumers( x );
x.UsingAmazonSqs( ( context, factoryConfigurator ) =>
{
factoryConfigurator.Host( new Uri( "amazonsqs://localhost:4566" ),
h =>
{
h.Config( new AmazonSimpleNotificationServiceConfig { ServiceURL = serviceUrl } );
h.Config( new AmazonSQSConfig { ServiceURL = serviceUrl } );
h.AccessKey( "test" );
h.SecretKey( "test" );
} );
RegisterReceiveEndpoints( factoryConfigurator, context );
} );
} );
protected override void RegisterConsumers( IRegistrationConfigurator configurator )
{
configurator.AddConsumer<MovementConsumer>();
}
protected override void RegisterReceiveEndpoints( IAmazonSqsBusFactoryConfigurator factoryConfigurator, IRegistrationContext context )
{
factoryConfigurator.ReceiveEndpoint( $"{ServiceConstants.ServiceName}-test-movement", endpointConfigurator =>
{
endpointConfigurator.ConfigureConsumer<MovementConsumer>( context );
endpointConfigurator.QueueSubscriptionAttributes["FilterPolicy"] = $"{{\"RoutingKey\": [\"null\"]}}";
} );
}
My first question with this approach is that is it necessary to re-register consumers? Ideally I'd like to call AddMassTransitTestHarness and just have it replace the already existing Bus with the TestHarness, but I was finding my consumers weren't being called. Having to re-register the endpoints in both the tests project and the actual project is a burden I'd like to avoid any other developers on this project having.
The second question I have is with regards to asserting against what's been published. I'm experiencing inconsistent results with the below code:
await busTestHarness.Start();
await busTestHarness.Bus.Publish( message, CancellationToken.None );
await busTestHarness.InactivityTask;
//await Task.Delay( TimeSpan.FromSeconds( 2 ) );
busTestHarness.Published.Select<T>().Any( publishedMessage => publishedMessage.Context.Headers.Any( headerValue => headerValue.Key == "RoutingKey" && (string) headerValue.Value == expectedHeader ) ).ShouldBeTrue();
Sometimes the above assertion fails. I am expecting my consumer to publish a new message with routing headers (I am not asserting against the message I publish in the test).
I've found that whilst still flakey, the 2 second delay seems to reduce the rate of failure. Is my usage of InactivityTask correct? Is there anything else I should be waiting for.
Thanks for any help, I've scoured the docs and watched the video on testing with WebApplicationFactory. I've mirrored what's done in there as best as I can. The major difference being that I am not expecting any responses from my messages.

Reset Database before each Test has problems with authentication in functional tests

I am implementing functional tests for my REST-Api. The Api is protected by authorization. For this I chose the json_login provider. So far, so good. Authentication works when accessing in the normal environment via Insomnia.
Now I want functional tests. For that, I create an user via the configured User-class and persist it in the database. Works as expected.
But of course the test only works once as the user already exists in the following tests.
So I tried hautelook/alice-bundle with ResetDatabaseTrait or ReloadDatabaseTrait as well as dmaicher/doctrine-test-bundle.
Both show the same behaviour: The authenticator can not find the newly created user. (EntityUserProvider::loadUserByUsername finds no user)
Apparently the EntityUserProvider seems to use a different "connection" into the database that can not look into the transaction those libraries started.
The entity-manager in my test that is responsible for persisting my user is created either with
protected function setUp(): void {
$kernel = self::bootKernel();
$this->em = $kernel->getContainer()
->get('doctrine')
->getManager();
}
or directly before creating the user with
$em = self::$container->get('doctrine')->getManager();
which seems correct for me. But in any case I get the same result -> "Invalid credentials" because the user can not be found.
Maybe someone out there can point me into the right direction?
After a refreshing break I remembered a detail when I was creating my tests. All the examples did not need a setUp-Method with self:bootKernel() in it. But without it the self::$container property was empty, so I added that to my test-class. Maybe there was the solution to the problem?
I was right!
I am using the Api-Platform package. Therefore my test-class is based in ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase. That class does not have a setUp Method, but inspecting createClient() I noticed that there the kernel is created by calling bootKernel() which also stops any running kernel.
So my setUp() method created a kernel. With that kernel I created my user.
Then I called createClient() to create the test-client for the requests. This killed my initial kernel and creates a new one which then leads to the problems.
Rearranging the statements - first create the client, then get the EntityManager from the now created container and create the User after creating the client solved the problem.
After two days , hooh
when you want to call multiple request, for example if you want at first request you get token and the second you call with this token and check auth, in during this calls if you use use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait trait your data base rest after first call, you have token but database is empty, and second call fail.
So, read again this important part of documentation :
There is one caveat though: in some tests, it is necessary to perform multiple requests in one test, for example when creating a user via the API and checking that a subsequent login using the same password works. However, the client will by default reboot the kernel, which will reset the database. You can prevent this by adding $client->disableReboot(); to such tests. Writing Functional Tests
I, know we are lazy developer and first read code, not document :-)
$client = static::createClient();
$client->disableReboot();
$manager = self::getContainer()->get('doctrine')->getManager();
$user = new User();
$user->setEmail('user#example.com');
$user->setPassword(
self::getContainer()->get('security.user_password_hasher')->hashPassword($user, $password = 'pass1234')
);
$manager->persist($user);
$manager->flush();
$response = $client->request('POST', '/authentication-token', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'email' => $user->getEmail(),
'password' => $password ,
],
]);
$token = $response->toArray()['token'] ?? null;
$client->request('GET', '/profile', [
'auth_bearer' => $token
]);
self::assertResponseIsSuccessful();

Symfony functional test: Current request not set when fetching service from container

One of my services depends on the HTTP_HOST value in the currentRequest object from the requestStack. When this service is used in a functional test it works because I create the client with the host parameter:
$client = static::createClient(array(), array(
'HTTP_HOST' => 'test.' . $this->domain
));
At some point I have the need to get a service from the container that has a dependency on the request so i thought i used the client created with the host value to fetch the service:
$client->getKernel()->getContainer()->get('service')->someMethod();
But the request object is no longer set when the constructer of this service is is called.
Is there any way I can use this service in the test function with a dependency on the Request object ?
Related code:
ControllerTest.php
//Create client with HTTP_HOST
$client = static::createClient(array(), array(
'HTTP_HOST' => 'test.' . $this->domain
));
//Do some request services depending on the request object work because the client is initiated with the HTTP_HOST value
$crawler = $client->request('GET', $redirectUrl);
$this->assertEquals(
1,
$crawler->filter('html:contains("feedback")')->count()
);
//Now I want to check if email feedback is send. This process starts in a EventSubsriber
//I have to trigger this event myself because the $event variable consist of fake data.
$client->getContainer()->get('event_subscriber')->process($event);
//now collect the mail and do some checks
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());
You should get the Container directly from your created client, as described in the official docs:
$client->getContainer()->get('service')->someMethod();
It may still be necessary to mock the whole service but more code examples would be needed..

Symfony2 Functonal Tests, query parameters are ignored

How do I send additional parameters with a request in a functional test in Symfony2. I have
$client = static::createClient();
$crawler = $client->request("GET", '/timezones/23.html?X=1', array("rest_auth" => "wrong"));
Both X and rest_auth are missing when the request hits my Symfony2 application. I have tried it with POST too and even with
json_encode(array("rest_auth" => "wrong"))
Nothing seems to work in sending additional query parameters to the request.
It turns out that this will work for the passed params
$req->get("timezone")
But this will not
$_REQUEST['timezone']
$_GET['timezone']

getRequest Method on Restful Server

I am hitting RestfulServer via an ajax call (url: BaseHref + "api/v1/Post/" + postId + '/PostTracks' to retrieve DataObject relations:
public function PostTracks(){
$controller = Controller::curr();
$request = $controller->getRequest();
$passkey = $request->getHeader('passkey');
$tracks = $this->owner->Tracks();
$set = array();
foreach($tracks as $track)
{
$set[] = array(
'm4aURL' => $track->m4a()->URL,
'oggURL' => $track->ogg()->URL,
'Title' => $track->Title
);
}
$this->outputJSON(200, $set);
}
At the top of the method I am trying to grab the value of a custom header that I sent in my ajax call via the beforeSend method. I have verified that the header is sent in the request to RestfulServer controller, but am having trouble getting the value.I am not getting anything for the value of $passkey.
How can I get header info from a RestfulServer controller. I don't understand why getRequest isn't working since RestfulServer extends from Controller.
You can use print_r($request->getHeaders()) to see all the headers attached to the request. In any case, I suspect the issue is with the casing of "passkey". By default SilverStripe will parse header names in CamelCaseFormat - so I suspect the header will be called Passkey or PassKey.
One nice way to debug issues with request is using Debug::dump($request->getHeaders()) or Debug::log($request->getHeaders()).
The latter will write a log file to the site that you can then track if you have terminal access to the server by "tail -f debug.log", or downloading them again and again.
That way you can see what logs out when you cant drirectly access the url.

Resources