How do I get started with mocking a web service in PHP? I'm currently directly querying the web API's in my unit testing class but it takes too long. Someone told me that you should just mock the service. But how do I go about that? I'm currently using PHPUnit.
What I have in mind is to simply save a static result (json or xml file) somewhere in the file system and write a class which reads from that file. Is that how mocking works? Can you point me out to resources which could help me with this. Is PHPUnit enough or do I need other tools? If PHPUnit is enough what part of PHPUnit do I need to check out? Thanks in advance!
You would mock the web service and then test what is returned. The hard coded data you are expecting back is correct, you set the Mock to return it, so then additional methods of your class may continue to work with the results. You may need Dependency Injection as well to help with the testing.
class WebService {
private $svc;
// Constructor Injection, pass the WebService object here
public function __construct($Service = NULL)
{
if(! is_null($Service) )
{
if($Service instanceof WebService)
{
$this->SetIWebService($Service);
}
}
}
function SetWebService(WebService $Service)
{
$this->svc = $Service
}
function DoWeb($Request)
{
$svc = $this->svc;
$Result = $svc->getResult($Request);
if ($Result->success == false)
$Result->Error = $this->GetErrorCode($Result->errorCode);
}
function GetErrorCode($errorCode) {
// do stuff
}
}
Test:
class WebServiceTest extends PHPUnit_Framework_TestCase
{
// Simple test for GetErrorCode to work Properly
public function testGetErrorCode()
{
$TestClass = new WebService();
$this->assertEquals('One', $TestClass->GetErrorCode(1));
$this->assertEquals('Two', $TestClass->GetErrorCode(2));
}
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testDoWebSericeCall()
{
// Create a mock for the WebService class,
// only mock the getResult() method.
$MockService = $this->getMock('WebService', array('getResult'));
// Set up the expectation for the getResult() method
$MockService->expects($this->any())
->method('getResult')
->will($this->returnValue(1)); // Change returnValue to your hard coded results
// Create Test Object - Pass our Mock as the service
$TestClass = new WebService($MockService);
// Or
// $TestClass = new WebService();
// $TestClass->SetWebServices($MockService);
// Test DoWeb
$WebString = 'Some String since we did not specify it to the Mock'; // Could be checked with the Mock functions
$this->assertEquals('One', $TestClass->DoWeb($WebString));
}
}
This mock may then be used in the other functions since the return is hard coded, your normal code would process the results and perform what work the code should (Format for display, etc...). This could also then have tests written for it.
Related
So I have a complicated onboarding process that does several steps. I created a class that handles the process but I've added a few more steps and I'd like to refactor this into something a bit more manageable. I refactored to use Laravel's pipeline, but feel this may not be the best refactor due to the output needing to be modified before each step.
Here is an example before and after with some pseudo code.
before
class OnboardingClass {
public $user;
public $conversation;
public function create($firstName, $lastName, $email){
// Step 1
$user = User::create();
// Step 2
$conversation = Conversation::create(); // store information for new user + existing user
// Step 3
$conversation->messages()->create(); // store a message on the conversation
// Step 4
// Send api request to analytics
// Step 5
// Send api request to other service
return $this;
}
}
after
class OnboardingClass{
public $user;
public $conversation;
public function create($firstName, $lastName, $email){
$data = ['first_name' => $firstName, ...]; // form data
$pipeline = app(Pipeline::Class);
$pipeline->send($data)
->through([
CreateUser::class,
CreateNewUserConversation::class,
AddWelcomeMessageToConversation::class,
...
])->then(function($data){
// set all properties returned from last class in pipeline.
$this->user = $data['user'];
$this->conversation = $data['conversation'];
});
return $this;
}
}
Now within each class I modify the previous data and output a modified version something like this
class CreateUser implements Pipe {
public function handle($data, Closure $next) {
// do some stuff
$user = User::create():
return $next([
'user' => $user,
'other' => 'something else'
]);
}
}
In my controller I am simply calling the create method.
class someController() {
public function store($request){
$onboarding = app(OnboardingClass::class);
$onboarding->create('John', 'Doe', 'john#example.com');
}
}
So the first pipe receives the raw form fields and outputs what the second pipe needs to get the job done in its class, then the next class outputs the data required by the next class, so on and so forth. The data that comes into each pipe is not the same each time and you cannot modify the order.
Feels a bit weird and I'm sure there is a cleaner way to handle this.
Any design pattern I can utilize to clean this up a bit?
I think you could try using Laravel Service Provider, for example, you could build a login service provider; or Event & Listener, for example, you could build an listener for login and triggers a event to handle all the necessary logics. Can't really tell which one is the best since outcome is the same and it makes same amount of network requests, but it's more on personal preferences
I have an API written using Symfony2 that I'm trying to write post hoc tests for. One of the endpoints uses an email service to send a password reset email to the user. I'd like to mock out this service so that I can check that the right information is sent to the service, and also prevent an email from actually being sent.
Here's the route I'm trying to test:
/**
* #Route("/me/password/resets")
* #Method({"POST"})
*/
public function requestResetAction(Request $request)
{
$userRepository = $this->get('app.repository.user_repository');
$userPasswordResetRepository = $this->get('app.repository.user_password_reset_repository');
$emailService = $this->get('app.service.email_service');
$authenticationLimitsService = $this->get('app.service.authentication_limits_service');
$now = new \DateTime();
$requestParams = $this->getRequestParams($request);
if (empty($requestParams->username)) {
throw new BadRequestHttpException("username parameter is missing");
}
$user = $userRepository->findOneByUsername($requestParams->username);
if ($user) {
if ($authenticationLimitsService->isUserBanned($user, $now)) {
throw new BadRequestHttpException("User temporarily banned because of repeated authentication failures");
}
$userPasswordResetRepository->deleteAllForUser($user);
$reset = $userPasswordResetRepository->createForUser($user);
$userPasswordResetRepository->saveUserPasswordReset($reset);
$authenticationLimitsService->logUserAction($user, UserAuthenticationLog::ACTION_PASSWORD_RESET, $now);
$emailService->sendPasswordResetEmail($user, $reset);
}
// We return 201 Created for every request so that we don't accidently
// leak the existence of usernames
return $this->jsonResponse("Created", $code=201);
}
I then have an ApiTestCase class that extends the Symfony WebTestCase to provide helper methods. This class contains a setup method that tries to mock the email service:
class ApiTestCase extends WebTestCase {
public function setup() {
$this->client = static::createClient(array(
'environment' => 'test'
));
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$this->mockEmailService = $mockEmailService;
}
And then in my actual test cases I'm trying to do something like this:
class CreatePasswordResetTest extends ApiTestCase {
public function testSendsEmail() {
$this->mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
}
So now the trick is to get the controller to use the mocked version of the email service. I have read about several different ways to achieve this, so far I've not had much luck.
Method 1: Use container->set()
See How to mock Symfony 2 service in a functional test?
In the setup() method tell the container what it should return when it's asked for the email service:
static::$kernel->getContainer()->set('app.service.email_service', $this->mockEmailService);
# or
$this->client->getContainer()->set('app.service.email_service', $this->mockEmailService);
This does not effect the controller at all. It still calls the original service. Some write ups I've seen mention that the mocked service is 'reset' after a single call. I'm not even seeing my first call mocked out so I'm not certain this issue is affecting me yet.
Is there another container I should be calling set on?
Or am I mocking out the service too late?
Method 2: AppTestKernel
See: http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html
See: Symfony2 phpunit functional test custom user authentication fails after redirect (session related)
This one pulls me out of my depth when it comes to PHP and Symfony2 stuff (I'm not really a PHP dev).
The goal seems to be to change some kind of foundation class of the website to allow my mock service to be injected very early in the request.
I have a new AppTestKernel:
<?php
// app/AppTestKernel.php
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
$this->kernelModifier = null;
};
}
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
// We force the kernel to shutdown to be sure the next request will boot it
$this->shutdown();
}
}
And a new method in my ApiTestCase:
// https://stackoverflow.com/a/19705215
protected static function getKernelClass(){
$dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
$finder = new Finder();
$finder->name('*TestKernel.php')->depth(0)->in($dir);
$results = iterator_to_array($finder);
if (!count($results)) {
throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.');
}
$file = current($results);
$class = $file->getBasename('.php');
require_once $file;
return $class;
}
Then I alter my setup() to use the kernel modifier:
public function setup() {
...
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
static::$kernel->setKernelModifier(function($kernel) use ($mockEmailService) {
$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
});
$this->mockEmailService = $mockEmailService;
}
This works! However I now can't access the container in my other tests when I'm trying to do something like this:
$c = $this->client->getKernel()->getContainer();
$repo = $c->get('app.repository.user_password_reset_repository');
$resets = $repo->findByUser($user);
The getContainer() method returns null.
Should I be using the container differently?
Do I need to inject the container into the new kernel? It extends the original kernel so I don't really know why/how it's any different when it comes to the container stuff.
Method 3: Replace the service in config_test.yml
See: Symfony/PHPUnit mock services
This method requires that I write a new service class that overrides the email service. Writing a fixed mock class like this seems less useful than a regular dynamic mock. How can I test that certain methods have been called with certain parameters?
Method 4: Setup everything inside the test
Going on #Matteo's suggestion I wrote a test that did this:
public function testSendsEmail() {
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
static::$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
$this->client->getContainer()->set('app.service.email_service', $mockEmailService);
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
This test fails because the expected method sendPasswordResetEmail wasn't called:
There was 1 failure:
1) Tests\Integration\Api\MePassword\CreatePasswordResetTest::testSendsEmail
Expectation failed for method name is equal to <string:sendPasswordResetEmail> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Thanks to Cered's advice I've managed to get something working that can test that the emails I expect to be sent actually are. I haven't been able to actually get the mocking to work so I'm a bit reluctant to mark this as "the" answer.
Here's a test that checks that an email is sent:
public function testSendsEmail() {
$this->client->enableProfiler();
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
$mailCollector = $this->client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
$this->assertInstanceOf('Swift_Message', $message);
$this->assertEquals('Reset your password', $message->getSubject());
$this->assertEquals('info#example.com', key($message->getFrom()));
$this->assertEquals($this->user->getEmail(), key($message->getTo()));
$this->assertContains(
'This link is valid for 24 hours only.',
$message->getBody()
);
$resets = $this->getResets($this->user);
$this->assertContains(
$resets[0]->getToken(),
$message->getBody()
);
}
It works by enabling the Symfony profiler and inspecting the swiftmailer service. It's documented here: http://symfony.com/doc/current/email/testing.html
during unit testing i'm always get confused about what to test.
Do i need to test the API and only the API or also the method result values.
class SomeEventHandler
{
public function onDispatch (Event $event)
{
if ($event->hasFoo)
{
$model = $this->createResponseModel('foo');
}
else
{
$model = $this->createResponseModel('bar');
}
// End.
return $model;
}
private function createResponseModel ($foo)
{
$vars = array(
'someVare' => true,
'foo' => $foo
);
// End.
return new ResponseModel($vars);
}
}
So should i test if the method onDispatch returns a instance of ResponseModel or should i also test if the variable foo is set properly?
Or is the test below just fine?
class SomeEventHandlerTest
{
// assume that a instance of SomeEventHandler is created
private $someEventHandler;
public function testOnDispatch_EventHasFoo_ReturnsResponseModel ()
{
$e = new Event();
$e->hasFoo = true;
$result = $someEventHandler->onDispatch($e);
$this->assertInstanceOf('ResponseModel', $result);
}
public function testOnDispatch_EventHasNoFoo_ReturnsResponseModel ()
{
$e = new Event();
$e->hasFoo = false;
$result = $someEventHandler->onDispatch($e);
$this->assertInstanceOf('ResponseModel', $result);
}
}
If you were checking the code by hand what is it that you would check? Just that a ResponseModel was returned or that it also had the proper values?
If you weren't writing tests and executed the code what would you look for to ensure that the code was doing what it was supposed to. You would check that the values in the returned object were correct. I would do that by using the public API of the object and verify that the values are right.
One idea is to have the tests such that if the code were deleted, you would be able to recreate all the functionality via only having the tests. Only checking the returned object could result in a function that just has return new ResponseModel();. This would pass the test but would not be what you want.
In short, what you decide to test is subjective, however you should at the minimum test all your public methods.
Many people limit their tests to public methods and simply ensure code coverage on the protected/private methods is adequate. However, feel free to test anything you think warrants a test. Generally speaking, the more tests the better.
In my opinion you should certainly test for your response data, not just the return type.
I rely on Unit Tests to let me make code changes in the future and be satisfied my changes have not created any breaks, just by running the tests.
So in your case, if the "foo" or "bar" response data is important, you should test it.
That way if you later change the response strings by accident, your tests will tell you.
I have an interesting problem and have searched the internet, but haven't yet found an answer.
I work for a company that doesn't allow it's workers to utilize OOP, it is kind of ridiculous, but the working experience is valuable.
Consider the following function:
function get_setting_values_from_file( $parameter )
{
exec("/usr/var/binary --options $parameter", $output, $return);
$settings = file( $output[0] );
foreach( $settings as $setting ) {
if( strstr( $setting, "color") ) {
$setting = explode( ":", $setting );
return $setting[1];
}
}
return false;
}
I need to unit test a similar function. I am currently using phpUnit for my tests and the vfsStream libraries to mock the file system, but how do you mock the call to exec("/usr/var/binary --options $parameter", $output, $return) when I'm developing with no access to the actual system? What is the recommend approach for dealing with test cases like this?
All feedback is appreciated.
You could mock exec() by using a function mock library. I made one (php-mock) for you which requires you to use namespaces
namespace foo;
use phpmock\phpunit\PHPMock;
class ExecTest extends \PHPUnit_Framework_TestCase
{
use PHPMock;
public function testExec()
{
$mock = $this->getFunctionMock(__NAMESPACE__, "exec");
$mock->expects($this->once())->willReturnCallback(
function ($command, &$output, &$return_var) {
$this->assertEquals("foo", $command);
$output = "failure";
$return_var = 1;
}
);
exec("foo", $output, $return_var);
$this->assertEquals("failure", $output);
$this->assertEquals(1, $return_var);
}
}
Simply mock this function to return the text that you are trying to get into $settings. You do not need to call the executable, simply create the file or return.
For instance, assuming the function get_setting_values_from_file() returns the settings as an array, you can simply mock the function in your test to return the settings as an array. Create a test stub to mock the object that contains the get_setting_values_from_file() method, and have that mock simply return the same FALSE, 1 or 2 that the test assumed.
$stub = $this->getMock('GetSettingsClass');
$stub->expects($this->any())
->method('get_settings_from_file')
->will($this->returnValue(0));
This is from the PHPUnit manual -> http://phpunit.de/manual/3.8/en/test-doubles.html#test-doubles.stubs
Optionally, you could even bypass the call, and simply test the functions/code that works on the returns by creating the array and passing it to those functions.
Assumed Example in the main code:
...
$settings = get_setting_values_from_file( 'UserType' );
$UserType = get_user_type($settings);
return $UserType;
function get_user_type($settings)
{
if($settings !== FALSE) // Returned from your function if parameter is not found
{
switch($settings)
{
case 1:
return 'User'; // Best to use Constants, but for example here only
break;
case 2:
return 'Admin';
break;
...
}
}
else
{
return FALSE;
}
}
Now, in your test, you can simply
$this->assertFalse(get_user_type(FALSE, 'Ensure not found data is handled properly as FALSE is returned');
$this->assertEqual('User', get_user_type(1), 'Test UserType=1');
$this->assertEqual('Admin', get_user_type(1), 'Test UserType=2');
...
These work as the code does not call the function that had to mock the read from the OS, but does handle all the expected returns by calling the function processing the setting return value. Here, you have simply assumed the return from the function 'get_setting_values_from_file()' without needing the file or any mocks.
This does NOT however test reading from the file, which I would do in another test by using the setUp and tearDown to actual create a file with the values you want (fopen/fwrite) and then call your function and ensure it returns what is expected.
I hope this helps to explain what I was thinking.
How can I check if an object will be successfully instantiated with the given argument, without actually creating the instance?
Actually I'm only checking (didn't tested this code, but should work fine...) the number of required parameters, ignoring types:
// Filter definition and arguments as per configuration
$filter = $container->getDefinition($serviceId);
$args = $activeFilters[$filterName];
// Check number of required arguments vs arguments in config
$constructor = $reflector->getConstructor();
$numRequired = $constructor->getNumberOfRequiredParameters();
$numSpecified = is_array($args) ? count($args) : 1;
if($numRequired < $numSpecified) {
throw new InvalidFilterDefinitionException(
$serviceId,
$numRequired,
$numSpecified
);
}
EDIT: $constructor can be null...
The short answer is that you simply cannot determine if a set of arguments will allow error-free instantiation of a constructor. As commenters have mentioned above, there's no way to know for sure if a class can be instantiated with a given argument list because there are runtime considerations that cannot be known without actually attempting
instantiation.
However, there is value in trying to instantiate a class from a list of constructor arguments. The most obvious use-case for this sort of operation is a configurable Dependency Injection Container (DIC). Unfortunately, this is a much more complicated operation than the OP suggests.
We need to determine for each argument in a supplied definition array whether or not it matches specified type-hints from the constructor method signature (if the method signature actually has type-hints). Also, we need to resolve how to treat default argument values. Additionally, for our code to be of any real use we need to allow the specification of "definitions" ahead of time for instantiating a class. A sophisticated treatment of the problem will also involve a pool of reflection objects (caching) to minimize the performance impact of repeatedly reflecting things.
Another hurdle is the fact that there's no way to access the type-hint of a reflected method parameter without calling its ReflectionParameter::getClass method and subsequently instantiating a reflection class from the returned class name (if null is returned the param has no type-hint). This is where caching generated reflections becomes particularly important for any real-world use-case.
The code below is a severely stripped-down version of my own string-based recursive dependency injection container. It's a mixture of pseudo-code and real-code (if you were hoping for free code to copy/paste you're out of luck). You'll see that the code below matches the associative array keys of "definition" arrays to the parameter names in the constructor signature.
The real code can be found over at the relevant github project page.
class Provider {
private $definitions;
public function define($class, array $definition) {
$class = strtolower($class);
$this->definitions[$class] = $definition;
}
public function make($class, array $definition = null) {
$class = strtolower($class);
if (is_null($definition) && isset($this->definitions[$class])) {
$definition = $this->definitions[$class];
}
$reflClass = new ReflectionClass($class);
$instanceArgs = $this->buildNewInstanceArgs($reflClass);
return $reflClass->newInstanceArgs($instanceArgs);
}
private function buildNewInstanceArgs(
ReflectionClass $reflClass,
array $definition
) {
$instanceArgs = array();
$reflCtor = $reflClass->getConstructor();
// IF no constructor exists we're done and should just
// return a new instance of $class:
// return $this->make($reflClass->name);
// otherwise ...
$reflCtorParams = $reflCtor->getParameters();
foreach ($reflCtorParams as $ctorParam) {
if (isset($definition[$ctorParam->name])) {
$instanceArgs[] = $this->make($definition[$ctorParam->name]);
continue;
}
$typeHint = $this->getParameterTypeHint($ctorParam);
if ($typeHint && $this->isInstantiable($typeHint)) {
// The typehint is instantiable, go ahead and make a new
// instance of it
$instanceArgs[] = $this->make($typeHint);
} elseif ($typeHint) {
// The typehint is abstract or an interface. We can't
// proceed because we already know we don't have a
// definition telling us which class to instantiate
throw Exception;
} elseif ($ctorParam->isDefaultValueAvailable()) {
// No typehint, try to use the default parameter value
$instanceArgs[] = $ctorParam->getDefaultValue();
} else {
// If all else fails, try passing in a NULL or something
$instanceArgs[] = NULL;
}
}
return $instanceArgs;
}
private function getParameterTypeHint(ReflectionParameter $param) {
// ... see the note about retrieving parameter typehints
// in the exposition ...
}
private function isInstantiable($class) {
// determine if the class typehint is abstract/interface
// RTM on reflection for how to do this
}
}