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.
Related
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);
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);
Suppose I have this class:
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
private function runTool($command)
{
return `/some/system/tool $command`;
}
[...]
}
I do not want to run system tool in my test, I want to replace a system call with predefined output.
So, the question is - should I create another class, move system call in it and mock that class in the test, or I can mock only that function of class which I will test?
Sure, both approaches will work, but which of them will be serve testing purposes better?
If you follow the single responsibility principle, you won't have this problem. Your class does not need to know how system calls are made, so you will have to use another class. You mock that.
IMO, in most cases when you need to mock protected or private methods, they do stuff that should be into another class and be mocked.
I would say it really depends on your infrastructure. Sometimes it is better to use Mock, sometimes Stub.
If the case is, that the class you want to test contains this unwanted method - use Mock and mock only this one function. That will make you sure, that any changes made to that class will be handled by the test.
If the unwanted function is a part of i.e. injected service or another class, which is not the domain of this particular test, you can create a stub.
You can't test private method, you can use a workaround and invoke it via reflection as described in this article and discussed in this SO QUESTION
But i suggest you to change the method visibility to protected and mock only the behaviour of the runTool method.
As example, suppose the following modified version of your class (i don't know how other method work so i suppose that you want to test their behaviour and take this implementation as example):
<?php
namespace Acme\DemoBundle\Service;
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
protected function runTool($command)
{
return `/some/system/tool $command`;
}
private function hasError($output)
{
return $output == "error";
}
private function parseOutput($output)
{
return json_decode($output);
}
}
As suppose the following test case:
<?php
namespace Acme\DemoBundle\Tests;
class SomeClassTest extends \PHPUnit_Framework_TestCase {
public function testCommandReturnError()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue("error"));
$this->assertFalse($mock->execute("commandName"));
}
public function testCommandReturnCorrectValue()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue('{"title":"myTitle"}'));
$returnValue = $mock->execute("commandName");
$this->assertEquals("myTitle", $returnValue->title);
}
}
Hope this help
I'm used to the habit of writing like this:
$results = SomeModelQuery::create()->filterByFoo('bar')->find();
However this does not scale for unit testing because I can't inject a mock object, i.e. I can't affect what data is returned. I'd like to use fixture data, but I can't.
Nor does it seem great to inject an object:
class Foo
{
public __construct($someModelQuery)
{
$this->someModelQuery = $someMOdelQuery;
}
public function doSthing()
{
$results = $this->someModelQuery->filterByFoo('bar')->find();
}
}
DI feels horrible. I have tens of query objects to mock and throw. Setting through constructor is ugly and painful. Setting using method is wrong because it can be forgotten when calling. And it feels painful to always for every single lib and action to create these query objects manually.
How would I elegantly do DI with PropelORM query classes? I don't want to call a method like:
$oneQuery = OneQuery::create();
$anotherQuery = AnotherQuery::create();
// ... 10 more ...
$foo = new Foo($oneQuery, $anotherQuery, ...);
$foo->callSomeFunctionThatNeedsThose();
In my opinion (and Martin Folowers's) there is a step between calling everything statically and using Dependency Injection and it may be what you are looking for.
Where I can't do full DI (Zend Framework MVC for example) I will use a Service Locator. A Service Layer will be the place that all your classes go to get there dependencies from. Think of it as a one layer deep abstraction for your classes dependencies. There are many benefits to using a Service Locator but I will focus on testability in this case.
Let's get into some code, here is are model query class
class SomeModelQuery
{
public function __call($method, $params)
{
if ($method == 'find') {
return 'Real Data';
}
return $this;
}
}
All it does is return itself unless the method 'find' is called. Then is will return the hard-coded string "Real Data".
Now our service locator:
class ServiceLocator
{
protected static $instance;
protected $someModelQuery;
public static function resetInstance()
{
static::$instance = null;
}
public static function instance()
{
if (self::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function getSomeModelQuery()
{
if ($this->someModelQuery === null) {
$this->someModelQuery = new SomeModelQuery();
}
return $this->someModelQuery;
}
public function setSomeModelQuery($someModelQuery)
{
$this->someModelQuery = $someModelQuery;
}
}
This does two things. Provides a global scope method instance so you can always get at it. Along with allowing it to be reset. Then providing get and set methods for the model query object. With lazy loading if it has not already been set.
Now the code that does the real work:
class Foo
{
public function doSomething()
{
return ServiceLocator::instance()
->getSomeModelQuery()->filterByFoo('bar')->find();
}
}
Foo calls the service locator, it then gets an instance of the query object from it and does the call it needs to on that query object.
So now we need to write some unit tests for all of this. Here it is:
class FooTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
ServiceLocator::resetInstance();
}
public function testNoMocking()
{
$foo = new Foo();
$this->assertEquals('Real Data', $foo->doSomething());
}
public function testWithMock()
{
// Create our mock with a random value
$rand = mt_rand();
$mock = $this->getMock('SomeModelQuery');
$mock->expects($this->any())
->method('__call')
->will($this->onConsecutiveCalls($mock, $rand));
// Place the mock in the service locator
ServiceLocator::instance()->setSomeModelQuery($mock);
// Do we get our random value back?
$foo = new Foo();
$this->assertEquals($rand, $foo->doSomething());
}
}
I've given an example where the real query code is called and where the query code is mocked.
So this gives you the ability to inject mocks with out needing to inject every dependency into the classes you want to unit test.
There are many ways to write the above code. Use it as a proof of concept and adapt it to your need.
I'm writing some test cases, and I've got a test case that is using Mock objects. I need to check to see if two class methods are called from another class method. Here's what I've done:
First I generated the Mock:
Mock::generate('Parser');
Then, inside my test I called:
$P = new MockParser();
$P->expectOnce('loadUrl', array('http://url'));
$P->expectOnce('parse');
$P->fetchAndParse('http://url');
My implementation code looks like:
public function fetchAndParse($url) {
$this->loadUrl($url);
$this->parse();
}
And the loadUrl and parse() methods definately exist. I'm getting two failures on my tests, both telling me "Expected call count for [loadUrl] was [1] got [0]". I've got no idea what's going on - the methods are being called from that function!
Thanks,
Jamie
While my experience has been with mocking frameworks in the .NET world, I think that what you're trying to do is incorrect.
Any mocking framework when asked to create a mock for a class, generates "stubs" for ALL the methods in that class. This includes the method fetchAndParse. So when you are calling fetchAndParse on your mock object $P, the methods loadUrl and parse are NOT called. What you are really doing is calling the "stubbed" fetchAndParse method.
I'm not really experienced in PHP, so I don't want to try and fix your test. Hopefully someone else can do that.
You misunderstood how mocking works. If you use dependency injection to set a helper object in your class, then you can mock your injected object. After that you can simulate the behavior (interface) of the original object. The better way is to mock interfaces, because you can develop without creating any class implementing the current interface.
By your example:
interface UrlLoaderInterface {
public function load($url);
}
class YourParser {
protected $urlLoader;
protected $source;
public function setUrlLoader(UrlLoaderInterface $urlLoader) {
$this->urlLoader = $urlLoader;
}
public function fetchAndParse($url) {
$this->loadUrl($url);
$this->parse();
}
public function loadUrl($url) {
$this->source = $this->urlLoader->load($url);
}
public function parse() {
}
}
Mock::generate('UrlLoaderInterface', 'MockUrlLoader');
class TestYourParser extends UnitTestCase {
public function testShouldCallUrlLoaderByFetchAndParse() {
$testUrl = 'http://url';
$urlLoader = new MockUrlLoader();
$urlLoader->expectOnce('load', array($testUrl));
$urlLoader->returns('load', 'source', array($testUrl));
$parser = new YourParser();
$parser->setUrlLoader($urlLoader);
$parser->fetchAndParse($testUrl);
}
}
Btw. your example is a fail, because method names cannot contain words like 'and', 'or', 'if', etc... A method is allowed to do only one thing. If you use these words then you can be sure that you have a bad designed code.