Dependency on mock method with PHPUnit - php

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.

Related

Which class should i mock service class or third party api of service class?

PostController's store method which calls the service class and service class calls the third party api i.e. line. while storing a post. i want to write testcase if the notify field is true then it sends notification to the user's through line if not then return with error message. i am not getting any idea how to perform this test. here is the code for PostController.php
private Post $post;
private PostService $service;
public function __construct(
PostService $service,
Post $post
) {
$this->service = $service;
$this->post = $post;
}
public function store(PostRequest $request): RedirectResponse
{
$post = $this->service->createPost($request->validated());
if ($request->notify) {
$message = 'lorem ipsum';
$this->service->lineSendToGroup($request->category_big_id, $message);
}
return redirect()->to('/posts')->with('success_message', 'Post created successfully.');
}
PostService.php
use App\Library\Line;
use App\Models\CategoryBig;
class PostService
{
private CategoryBig $categoryBig;
public function __construct(
CategoryBig $categoryBig,
) {
$this->categoryBig = $categoryBig;
}
public function lineSendToGroup(int $categoryBigId, string $message): void
{
$catB = $this->findOrFailCategoryBig($categoryBigId);
Line::send(
$catB->line_message_channel_secret,
$catB->line_message_channel_access_token,
$catB->line_group_id,
$message
);
}
public function findOrFailCategoryBig(int $categoryBigId): CategoryBig
{
return $this->categoryBig->whereId($categoryBigId)->firstOrFail();
}
public function createPost(array $createData): Post
{
$createData += [$data];
return $this->greeting->create($createData);
}
Line.php
namespace App\Library;
use Illuminate\Support\Carbon;
use LINE\LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\Event\MessageEvent\TextMessage;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;
use Log;
class Line
{
public static function send($channel_secret, $access_token, $line_user_id, $message)
{
$http_client = new CurlHTTPClient($access_token);
$bot = new LINEBot($http_client, ['channelSecret' => $channel_secret]);
$textMessageBuilder = new TextMessageBuilder($message);
$response = $bot->pushMessage($line_user_id, $textMessageBuilder);
if ($response->isSucceeded()) {
return true;
} else {
return false;
}
}
}
Test
public function test_notification_when_LINE_notification_is_specified()
{
}
Step 1)
You can write a unit test for Line class.
For ease of use, you can refactor this class and use Laravel Http client instead. Create different stubs for each line's response (e.g 200) then use Laravel testing helpers to mock resposne.
Step 2)
Write a feature test for your controller. In this test you don't care about internal functionality of Line class and just want to make sure method of this class is called with proper arguments.
You can use mockery to achieve that.
Migrate to non-static method on Line class. You can't mock static methods.
For happy path test:
$mock = Mockery::mock(\App\Library\Line::class);
$mock->shouldReceive('send')
->once()
->with($secret, $token, $groupId, $message)
->andReturn(true);
// of course mocking object doesn't do much alone and you need to bind and resolve it.
$this->app->instance(\App\Library\Line::class, $mock);
Then resolve it inside your PostService#lineSendToGroup
resolve(Line::class, [
'token' => $token,
// ... other args
])
For opposite scenario use shouldNotReceive('send') instead.

Pimple ArgumentCountError: Too few arguments to function

I'm trying to understand dependency injection, and I in theory, I get it, but, I wanted to make an example just to help me. However, I'm getting the following error
PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function Main\Services\UserService::__construct(), 0 passed
in ...
Here's my "main" file, I've called it index.php
<?php
#index.php
require_once 'vendor/autoload.php';
use Main\Controllers\UserController;
use Main\Services\UserService;
use Main\Models\UserModel;
use Pimple\Container;
$container = new Container;
$container['UserModel'] = function($c) {
return new UserModel();
};
$container['UserService'] = function ($c) {
return new UserService($c['UserModel']);
};
$container['UserController'] = function ($c) {
echo "creating a new UserController\n";
$aUserService = $c['UserService'];
return new UserController($aUserService);
};
$myUserService = new $container['UserService'];
$myResult = $myUserService->parseGet();
echo $myResult, PHP_EOL;
Here's the model that's being passed into the service
<?php
# Models/UserModel.php
namespace Main\Models;
class UserModel
{
private $record;
public function getRecord()
{
return [
'first_name' => 'Bob',
'last_name' => 'Jones',
'email' => 'bj#example.com',
'date_joined' => '11-12-2014',
];
}
}
And, here's the service, which takes the model as it's constructor argument
<?php
namespace Main\Services;
use Main\Models\UserModel;
class UserService
{
private $userModel;
public function __construct(UserModel $userModel)
{
echo "verifying that the userModel passed in was a valid UserModel\n";
$this->userModel = $userModel;
print_r($this->userModel->getRecord());
}
public function parseGet()
{
$retVal = $this->userModel->getRecord();
return json_encode($retVal);
}
}
So, in theory, Pimple should be able to instantiate a UserService object. I even verified that the UserModel passed into the UserService class is a valid UserModel object (which it is as is evident that it prints out an array)
What am I missing? Is there something that I have failed to account for?
Oh, here's the composer.json file
{
"require": {
"pimple/pimple": "~3.0"
},
"autoload": {
"psr-4": {
"Main\\" : "./"
}
}
}
I made a gitHub link so the project can be checked out and ran without having to copy past everything (https://github.com/gitKearney/pimple-example)
SOLUTION
The problem was I have an extra new in the line
$myUserService = new $container['UserService'];
It was so obvious, I couldn't see it
$container['UserService'] already is a UserService object. check yer service definition:
$container['UserService'] = function ($c) {
return new UserService($c['UserModel']);
};
That sets $container['UserService'] to be return new UserService($c['UserModel']) when it's invoked, right?
Your code is basically going:
$o1 = new UserService($c['UserModel']);
$o2 = new $o2;
You use dependency injection containers to free yourself form the pain of manipulating objects' dependencies. Creating a new UserService is not necessary (if it really is a service). In that case, you define it in your $container once, and use it whenever you need to.
So instead of creating a new UserService object and calling its method parseGet() (what you did in your code) you can do something like this:
$myResult = $container['UserService']->parseGet();
When you define something like:
$container['UserService'] = function ($c) {
return new UserService($c['UserModel']);
};
you are telling Pimple how to handle creation of a UserService once you try to access $container['UserService']
That's why you define dependencies as functions.
This might be related to your question Why use a closure for assignment instead of directly assigning a value to a key?

PHPUnit mocked method returns null

I am trying to test the below class using PHPUnit
class stripe extends paymentValidator {
public $apiKey;
public function __construct ($apiKey){
$this->apiKey = $apiKey;
}
public function charge($token) {
try {
return $this->requestStripe($token);
} catch(\Stripe\Error\Card $e) {
echo $e->getMessage();
return false;
}
}
public function requestStripe($token) {
// do something
}
}
My test scripts is like the below:
class paymentvalidatorTest extends PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function test_stripe() {
// Create a stub for the SomeClass class.
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe', 'charge'])
->getMock();
$stripe->expects($this->any())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
}
}
With my test script I was expecting the test double of stripe::charge() method will do exactly as the defined in the original class and the stripe::requestStripe() will return 'Miaw'. Therefore, $stripe->charge('token') should also return 'Miaw'. However, when I run the test I get:
Failed asserting that null matches expected 'Miaw'.
How should I fix this ?
Where you're calling setMethods, you're telling PHPUnit that the mock class should mock the behaviour of those methods:
->setMethods(['requestStripe', 'charge'])
In your case it looks like you want to partially mock the class, so that requestStripe() returns Miaw, but you want charge to run its original code - you should just remove charge from the mocked methods:
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe'])
->getMock();
$stripe->expects($this->once())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
While you're at it you may as well specify how many times you expect requestStripe() to be called - it's an extra assertion with no extra effort, as using $this->any() doesn't provide you with any added benefit. I've included using $this->once() in the example.

ZF2 phpunit Zend Logger

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);

Why doesn't this mock the logging call?

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);

Categories