So I am not sure what I am doing wrong but:
class Details {
public function details($href) {
$client = new Client();
$eveLog = new EveLogHandler();
$response = $client->request('GET', $href);
$eveLog->requestLog($response, 'eveonline_item_details.log');
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody()->getContents());
}
return false;
}
}
I want to mock this: $eveLog->requestLog($response, 'eveonline_item_details.log');
So I wrote a test:
class DetailsTest extends \PHPUnit_Framework_TestCase {
public function getLogMock() {
return $this->getMockBuilder('EveOnline\Logging\EveLogHandler')
->setMethods(['requestLog'])
->getMock();
}
public function testDetailsDoesNotReturnFalse() {
$details = new EveOnline\Items\Details();
$logger = $this->getLogMock();
$logger->expects($this->exactly(1))
->method('requestLog')
->with('request', 'request.log');
$response = $details->details('http://example.com');
$this->assertNotFalse($response);
}
}
Accept it doesnt mock the method call, instead it freaks out with an error:
1) DetailsTest::testDetailsDoesNotReturnFalse
Error: Call to undefined function EveOnline\Logging\storage_path()
Now this method: storage_path is a laravel method and since this library is out side of laravel, for development purposes, the method doesn't exist, hence me trying to mock the call to: $eveLog->requestLog($response, 'eveonline_item_details.log');
Two questions arise from this:
First of all why doesn't my test pick up on the fact that I have mocked the method call?
When it coms time to test this particular method how do you mock global functions? Laravel has a few of them and I'll need mock out the storage_path method.
Ideas?
update
It should be known that this class gets turned into a laravel facade via a provider class and a facade class that both get registered with laravel allowing me to do something like
EveDetails::details()
For example. Learn more here https://laravel.com/docs/5.1/facades
I can post the facade and provider class of that will help.
The only reason the class is instantiated in my ear is because this particular component is being built as a separate installable component of laravel.
Your method Details::details() doesn't know that $eveLog should be an instance of your mock instead of class EveLogHandler. You have to inject the instance of your logger by using dependency injection.
The simpliest way to do this is to put it to the constructor:
class Details {
private $eveLog;
public function __construct($eveLog) {
$this->eveLog = $eveLog;
}
public function details($href) {
$client = new Client();
$response = $client->request('GET', $href);
$this->eveLog->requestLog($response, 'eveonline_item_details.log');
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody()->getContents());
}
return false;
}
}
In your production code you'll have to call:
$details = new EveOnline\Items\Details(new EveLogHandler());
to inject the real Eventlogger (an instance of class EveLogHandler) to the instance of class Details.
In your TestCase you'll now have inject your mock:
$logger = $this->getLogMock();
$logger->expects($this->exactly(1))
->method('requestLog')
->with('request', 'request.log');
$details = new EveOnline\Items\Details($logger);
Related
I'm trying to write a test for a method in the class below. However, when I run the test I get the error that get_b64 is never run? I don't see how this is not running.
I've had a little look into the mockery documentation for testing static methods, but as far as I can tell this error isn't due to that?
What do I need to change with my testing strategy or be able to mock the function call in the mocked object?
Class:
namespace App\Services\Steam;
use App\Services\Steam\Utils;
class Steam
{
public function profile(string $steamID)
{
$b64 = Utils::get_b64($steamID);
if ($b64 === null) {
throw new \App\Exceptions\InvalidSteamId();
}
return new Profile($b64);
}
}
TestCase:
public function test_create_user_object()
{
$id = "123"
$utilsMock = Mockery::mock(\App\Services\Steam\Utils::class);
$utilsMock->shouldReceive('get_b64')
->once()
->with($id)
->andReturn($id);
$steam = new \App\Services\Steam\Steam();
$steam->profile($id);
}
You call get_b64 statically, which means it is called from the class, not an object.
To mock such calls you need to use aliases:
public function test_create_user_object()
{
$id = "123"
$utilsMock = Mockery::mock('alias:\App\Services\Steam\Utils');
$utilsMock->shouldReceive('get_b64')
->once()
->with($id)
->andReturn($id);
$steam = new \App\Services\Steam\Steam();
$steam->profile($id);
}
Bear in mind that it completely replaces the Utils class, so if you have more static functions called from the class, you need to mock them as well.
I am trying to write unit test for my application. which as logging the information functionality.
To start with i have service called LogInfo, this how my class look like
use Zend\Log\Logger;
class LogInfo {
$logger = new Logger;
return $logger;
}
I have another class which will process data. which is below.
class Processor
{
public $log;
public function processData($file)
{
$this->log = $this->getLoggerObj('data');
$this->log->info("Received File");
}
public function getLoggerObj($logType)
{
return $this->getServiceLocator()->get('Processor\Service\LogInfo')->logger($logType);
}
}
here i am calling service Loginfo and using it and writing information in a file.
now i need to write phpunit for class Processor
below is my unit test cases
class ProcessorTest{
public function setUp() {
$mockLog = $this->getMockBuilder('FileProcessor\Service\LogInfo', array('logger'))->disableOriginalConstructor()->getMock();
$mockLogger = $this->getMockBuilder('Zend\Log\Logger', array('info'))->disableOriginalConstructor()->getMock();
$serviceManager = new ServiceManager();
$serviceManager->setService('FileProcessor\Service\LogInfo', $mockLog);
$serviceManager->setService('Zend\Log\Logger', $mockLogger);
$this->fileProcessor = new Processor();
$this->fileProcessor->setServiceLocator($serviceManager);
}
public function testProcess() {
$data = 'I have data here';
$this->fileProcessor->processData($data);
}
}
I try to run it, i am getting an error "......PHP Fatal error: Call to a member function info() on a non-object in"
i am not sure , how can i mock Zend logger and pass it to class.
Lets check out some of your code first, starting with the actual test class ProcessorTest. This class constructs a new ServiceManager(). This means you are going to have to do this in every test class, which is not efficient (DRY). I would suggest constructing the ServiceMananger like the Zend Framework 2 documentation describes in the headline Bootstrapping your tests. The following code is the method we are interested in.
public static function getServiceManager()
{
return static::$serviceManager;
}
Using this approach makes it possible to obtain the instance of ServiceManager through Bootstrap::getServiceManager(). Lets refactor the test class using this method.
class ProcessorTest
{
protected $serviceManager;
protected $fileProcessor;
public function setUp()
{
$this->serviceManager = Bootstrap::getServiceManager();
$this->serviceManager->setAllowOverride(true);
$fileProcessor = new Processor();
$fileProcessor->setServiceLocator($this->serviceManager);
$this->fileProcessor = $fileProcessor;
}
public function testProcess()
{
$mockLog = $this->getMockBuilder('FileProcessor\Service\LogInfo', array('logger'))
->disableOriginalConstructor()
->getMock();
$mockLogger = $this->getMockBuilder('Zend\Log\Logger', array('info'))
->disableOriginalConstructor()
->getMock();
$serviceManager->setService('FileProcessor\Service\LogInfo', $mockLog);
$serviceManager->setService('Zend\Log\Logger', $mockLogger);
$data = 'I have data here';
$this->fileProcessor->processData($data);
}
}
This method also makes it possible to change expectations on the mock objects per test function. The Processor instance is constructed in ProcessorTest::setUp() which should be possible in this case.
Any way this does not solve your problem yet. I can see Processor::getLoggerObj() asks the ServiceManager for the service 'Processor\Service\LogInfo' but your test class does not set this instance anywhere. Make sure you set this service in your test class like the following example.
$this->serviceManager->setService('Processor\Service\LogInfo', $processor);
I'm attempting to write PHPUnit tests for an Email abstraction class i'm using. The class interacts with the Mailgun API but I don't want to touch this in my test, I just want to return the response I would expect from Mailgun.
Within my test I have a setup method:
class EmailTest extends PHPUnit_Framework_TestCase
{
private $emailService;
public function setUp()
{
$mailgun = $this->getMockBuilder('SlmMail\Service\MailgunService')
->disableOriginalConstructor()
->getMock();
$mailgun->method('send')
->willReturn('<2342423#sandbox54533434.mailgun.org>');
$this->emailService = new Email($mailgun);
parent::setUp();
}
public function testEmailServiceCanSend()
{
$output = $this->emailService->send("me#test.com");
var_dump($output);
}
}
This is the basic outline of the email class
use Zend\Http\Exception\RuntimeException as ZendRuntimeException;
use Zend\Mail\Message;
use SlmMail\Service\MailgunService;
class Email
{
public function __construct($service = MailgunService::class){
$config = ['domain' => $this->domain, 'key' => $this->key];
$this->service = new $service($config['domain'], $config['key']);
}
public function send($to){
$message = new Message;
$message->setTo($to);
$message->setSubject("test subject");
$message->setFrom($this->fromAddress);
$message->setBody("test content");
try {
$result = $this->service->send($message);
return $result;
} catch(ZendRuntimeException $e) {
/**
* HTTP exception - (probably) triggered by network connectivity issue with Mailgun
*/
$error = $e->getMessage();
}
}
}
var_dump($output); is currently outputting NULL rather than the string i'm expecting. The method send i'm stubbing in the mock object has a dependency through an argument, and when I call $mailgun->send() directly it errors based on this so I wonder if this is what is failing behind the scenes. Is there a way to pass this argument in, or should I approach this a different way?
It is strange it does not throw an exception in Email::__construct.
The expected parameter is a string, and the MailgunService object is instantiated within the email constructor. In your test you are passing the object, so I would expect and error at line
$this->service = new $service($config['domain'], $config['key']);
What you need is:
class Email
{
public function __construct($service = null){
$config = ['domain' => $this->domain, 'key' => $this->key];
$this->service = $service?: new MailgunService($config['domain'], $config['key']);
}
Also, it may not be a good idea to catch an exception and return nothing in Email::send.
Hello
folks,
I wrote a low level implementation for a XmlRPC-Api and I've trouble to test the communication.
Here is my code.
abstract class BaseClient
{
protected function call($method, array $params = array())
{
$request = xmlrpc_encode_request($method, $parameters);
$file = file_get_contents($this->getDSN(), false, $context);
$response = xmlrpc_decode($file);
if ($response && xmlrpc_is_fault(array($response))) {
trigger_error("xmlrpc: {$response[faultString]} ({$response[faultCode]})");
}
return $response;
}
}
Client extends BaseClient
{
public function testCall($msg)
{
return $this->call('testMethid', array($msg));
}
}
And here is my Test.
ClientTest extends PHPUnit_FrameWork_TestCase
{
public function testTestCall()
{
$c = new Client();
$resp = $c->testCall('Hello World');
$this->assertEquals('Hello World', $resp);
}
}
This test will crash every time, because its not possible to access the api inside a testing environment.
I can't see a solution to mock and inject the call function. What can I do? Maybe my object structure is bad and not able to test
and how can I improve this structure (if this happen)?
Cheers.
Since you're trying to test an external API, I would begin by wrapping your file_get_contents() call in another class and injecting that into your BaseClient. In the simplest form, it might look something like this:
class RemoteFileRetriever
{
public function retrieveFileContents($url)
{
// Do some work to create $context
...
// Now grab the file contents
$contents = file_get_contents($url, false, $context);
return $contents;
}
}
abstract class BaseClient
{
private $fileRetriever;
public function __construct(RemoteFileRetriever $fileRetriever)
{
$this->fileRetriever = $fileRetriever;
}
protected function call($method, array $params = array())
{
...
$file = $this->fileRetriever->retrieveFileContents($this->getDSN());
...
}
}
Now in your test, you can use a mock object to inject as the file retriever. E.g.:
class ClientTest extends PHPUnit_FrameWork_TestCase
{
public function testTestCall()
{
$mockRetriever = new MockRemoteFileRetriever();
$c = new Client($mockRetriever);
$resp = $c->testCall('Hello World');
$this->assertEquals('Hello World', $resp);
}
}
PHPUnit atually has some built-in helpers for mocking. See PHPUnit's Mock Objects.
You don't want to mock the call function.
If you can't setup a fake service then you want to mock the php functions which you can do using PHP Namespacing (Have to have PHP 5.3). You can then create mocks for internal php functions that you are calling in your call method.
http://www.schmengler-se.de/-php-mocking-built-in-functions-like-time-in-unit-tests
If you are not able to do this, testing can be pretty difficult. Can you create a fake api that you can hit for testing? Remember that you aren't actually testing the methods of the api, rather you are trying to make sure that you code makes the request to the api and handles the response in the manner you intend.
As a rule, assume that third party code has been tested and works properly.
I'm working on a project that allows users to interact via SMS text messages. I've subclassed Zend Framework's request and response objects to get requests from an SMS API and then send back responses. It works when I "test" it via the development environment but I'd really like to do unit testing.
But inside the test case class, it's not using my request object, it's using Zend_Controller_Request_HttpTestCase. I'm pretty sure I'd have the same problem with the response object, I'm just not at that point yet.
My simplified test class:
class Sms_IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {
...
public function testHelpMessage() {
// will output "Zend_Controller_Request_HttpTestCase"
print get_class($this->getRequest());
...
}
}
If I override the request and response object before running the tests like so:
public function setUp()
{
$this->bootstrap = new Zend_Application(APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini');
parent::setUp();
$this->_request = new Sms_Model_Request();
$this->_response = new Sms_Model_Response();
}
I'm unable to set up my tests by using the methods in Zend_Controller_Request_HttpTestCase like setMethod and setRawBody before calling the front controller to dispatch.
How can I unit test my controllers after I've subclassed the request and response objects?
You can try to define getRequest and getResponse methods in your Sms_IndexControllerTest, like:
public function getRequest()
{
if (null === $this->_request) {
$this->_request = new Sms_Model_Request;
}
return $this->_request;
}
public function getResponse()
{
if (null === $this->_response) {
$this->_response = new Sms_Model_Response;
}
return $this->_response;
}
What I ended up doing was copying the entire code of the Request and Response test case objects into subclassed versions of my own request and response classes. Here's the gist of the request object:
Sublcassing the Request and Response objects and pasting the entire code of RequestTestCase:
class MyApp_Controller_Request_SmsifiedTestCase
extends MyApp_Controller_Request_Smsified {
// pasted code content of RequestTestCase
}
and then setting them in the setUp() function of the ControllerTest:
class Sms_IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {
{
...
public function setUp()
{
$this->bootstrap =
new Zend_Application(APPLICATION_ENV, APPLICATION_PATH
. '/configs/application.ini');
parent::setUp();
$this->_request =
new MyApp_Controller_Request_SmsifiedTestCase();
$this->_response =
new MyApp_Controller_Response_SmsifiedTestCase();
}
...
}
Then it worked.