I'm new to PHPUnit and i'm wondering how to assert that a function from a service has been called.
I tried to mock my service that implement the function ofDatetimeRange :
$mock = $this->getMockBuilder(QueryBuilder::class)
->onlyMethods(['ofDatetimeRange'])
->getMock();
And then just call the function that suppose to call the service and finally assert that have been call once or never.
Here is the test case :
/**
* Test that there is no date filter.
*
* #return void
*/
public function testNoDateFilter()
{
$mock = $this->getMockBuilder(QueryBuilder::class)
->onlyMethods(['ofDatetimeRange'])
->getMock();
$engine = new Engine();
$engine->prepareQuery(); <--- this should call QueryBuilder::ofDatetimeRange
$mock->expects($this->exactly(1))->method('ofDatetimeRange');
}
Expectation failed for method name is "ofDatetimeRange" when invoked 1
time(s). Method was expected to be called 1 times, actually called 0
times.
It looks like my engine doesn't use the mocked instance...
Is there something i'm doing wrong ?
Note that $engine->prepareQuery() should call ofDatetimeRange method of QueryBuilder class.
You're right, $engine object in your test doesn't use the mock object.
I'm not sure how exactly Engine class looks like, but to be able to use mock you usualy should design your class to have mocked object dependency.
class Engine
{
private QueryBuilder $qb;
public function __construct(QueryBuilder $qb)
{
$this->qb = $qb;
}
}
And then you pass the mock object in your test case:
$engine = new Engine($mock);
This should work.
Related
I have an issue returning correct behaviour when mocking specific object while writing tests in Symfony 5.
My method:
public function myTestFunction(TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
.....
}
I was trying in my tests like:
$this->token = $this->createMock(TokenInterface::class);
$this->token
->method('getUser')
->willReturn(UserInterface::class);
Result does not replicate the behaviour I want to accomplished based on the code I posted.
As I am defining $token as a stub, I am forced to describe every interaction with it, otherwise PHPUnit, will return null for every method call.
In your ->willReturn call you should pass the real object instead of string of the class name. This is what I see wrong in your current code.
So you should create real User class which is an instance of the UserInterface and pass this object into ->willReturn($realUserObject).
I think it should help you.
I have model class that calls mailer class inside one of its methods:
class someModel{
public function sendEmail($data){
$mailer = new Mailer();
$mailer->setFrom($data['from']);
$mailer->setTo($data['to']);
$mailer->setSubject($data['subject']);
return $mailer->send();
}
}
How can I test sendEmail method? Maybe I should mock mailer class and check if all these mailer methods were called in sendMail method?
Your help would be appreciated.
IMO wrapping the Mailer class does not solve the problem you're facing, which is you don't have control over the Mail instance being used.
The problem comes from creating the dependencies inside the object that needs them instead of injecting them externally like this:
class someModel{
private $mailer;
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function sendEmail($data){
$this->mailer->setFrom($data['from']);
$this->mailer->setTo($data['to']);
$this->mailer->setSubject($data['subject']);
return $this->mailer->send();
}
}
When creating the someModel instance, you must pass a Mail instance (which is an external dependency). And in the test you can pass a Mail mock that will check that the correct calls are being made.
Alternative:
If you feel that injecting a Mail instance is bad (maybe because there are lots of someModel instances), or you just can't change your code this way, then you could use a Services repository, that will keep a single Mail instance and that allows you to set it externally (again, in the test you would set a mock).
Try a simple one like Pimple.
I would (and have in my own code with Mailer!) wrap your instance of Mailer inside a class that you write. In other words, make your own Email class that uses Mailer under the hood. That allows you to simplify the interface of Mailer down to just what you need and more easily mock it. It also gives you the ability to replace Mailer seamlessly at a later date.
The most important thing to keep in mind when you wrap classes to hide external dependencies is keep the wrapper class simple. It's only purpose is to let you swap out the Email libraries class, not provide any complicated logic.
Example:
class Emailer {
private $mailer = new Mailer();
public function send($to, $from, $subject, $data) {
$this->mailer->setFrom($from);
$this->mailer->setTo($to);
...
return $mailer->send();
}
}
class EmailerMock extends Emailer {
public function send($to, $from, $subject, $data) {
... Store whatever test data you want to verify ...
}
//Accessors for testing the right data was sent in your unit test
public function getTo() { ... }
...
}
I follow the same pattern for all classes/libraries that want to touch things external to my software. Other good candidates are database connections, web services connections, cache connections, etc.
EDIT:
gontrollez raised a good point in his answer about dependency injection. I failed to explicitly mention it, but after creating the wrapper the way you would want to use some form of dependency injection to get it into the code where you want to use it. Passing in the instance makes it possible to setup the test case with a Mocked instance.
One method of doing this is passing in the instance to the constructor as gontrollez recommends. There are a lot of cases where that is the best way to do it. However, for "external services" that I am mocking I found that method became tedious because so many classes ended up needing the instance passed in. Consider for example a database driver that you want to Mock for your tests, but you use in many many different classes. So instead what I do is create a singleton class with a method that lets me mock the whole thing at once. Any client code can then just use the singleton to get access to a service without knowing that it was mocked. It looked something like this:
class Externals {
static private $instance = null;
private $db = null;
private $email = null;
...
private function __construct() {
$this->db = new RealDB();
$this->mail = new RealMail();
}
static function initTest() {
self::get(); //Ensure instance created
$db = new MockDB();
$email = new MockEmail();
}
static function get() {
if(!self::$instance)
self::$instance = new Externals();
return self::$instance;
}
function getDB() { return $this->db; }
function getMail() { return $this->mail; }
....
}
Then you can use phpunit's bootstrap file feature to call Externals::initTest() and all your tests will be setup with the mocked externals!
First, as RyanW says, you should write your own wrapper for Mailer.
Second, to test it, use a mock:
<?php
class someModelTest extends \PHPUnit_Framework_TestCase
{
public function testSendEmail()
{
// Mock the class so we can verify that the methods are called
$model = $this->getMock('someModel', array('setFrom', 'setTo', 'setSubject', 'send'));
$controller->expects($this->once())
->method('setFrom');
$controller->expects($this->once())
->method('setTo');
$controller->expects($this->once())
->method('setSubject');
$controller->expects($this->once())
->method('send');
$model->sendEmail();
}
}
The above code is untested, but it basically mocks the someModel class, creating dummy functions for each each function called within sendEmail. It then tests to make sure each of the functions called by sendEmail is called exactly once when sendEmail is called.
See the PHPUnit docs for more info on mocking.
I'm creating a small bundle for Symfony2, which does nothing more than provide a service; it can be called to insert a line into the database, containing a few parameters:
class AuditLogService {
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function log($type, $channel, $message) {
$log = new AuditLog();
$log->setType($type);
$log->setChannel($channel);
$log->setMessage($message);
$this->em->persist($log);
$this->em->flush();
}
}
Now I need to write a test for it, so basically here are the steps I'm assuming I should take:
write a test that gets logger service, but uses a mock entityManager instead?
call function on that logger which uses the mocked em
try to fetch it back from the mocked em, using the parameters I used in step 2
assert if I find a result or not
I'm pretty sure that in theory these are the correct steps, but can anyone point me into the right direction as to how to actually do it?
I have this:
public function testServiceLoaded()
{
$this->assertEquals(get_class($this->container->get('bbit_audit_log.service')), 'BBIT\AuditLogBundle\Services\AuditLogService');
}
This comes back with the following error:
"bbit_audit_log.service" has a dependency on a non-existent service "doctrine.orm.entity_manager"
It seems like my container does not contain the entitymanager?
In unit test skip 3 and 4. It's not responsibility of AuditLogService to save in the database (but to call em). 3 and 4 should be tested by functional test.
I am trying to run a parametrized tests... Was trying to implement it like it explained here:
http://docs.flexunit.org/index.php?title=Parameterized_Test_Styles
Here is what my test case looking
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
[Paremeters]
public static var stackProvider:Array = [new ArrayBasedStack(), new LinkedListBasedStack()] ;
private var _stack:IStack;
public function ArrayBasedStackTests(param:IStack)
{
_stack = param;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stackProvider" )]
public function isEmptyStackPositiveTest():void
{
var stack:IStack = _stack;
assertEquals( true, stack.isEmpty() );
}
But this code throws following initializing Error:
Error: Custom runner class org.flexunit.runners.Parameterized should
be linked into project and implement IRunner. Further it needs to have
a constructor which either just accepts the class, or the class and a
builder.
Need help to fix it
UPDATE
I've updated the code so it looks like this
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
private var foo:Parameterized;
[Parameters]
public static function stacks():Array
{
return [ [new ArrayBasedStack()], [new LinkedListBasedStack()] ] ;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stacks")]
public function isEmptyStackPositiveTest(stack:IStack):void
{
assertEquals( true, _stack.isEmpty() );
}
It works. But the result is a bit strange. I have 4 test executed instead of 2. (I have 2 items in data provider, so cant get why do I have 4 tests).
Output
http://screencast.com/t/G8DHbcjDUkJ
The [Parameters] meta-data specifies that the parameters are passed to the constructor of the test - so the test class is called for each parameter. You also have the dataProvider set for the specific test method, so the test method is also called once for each parameter. Two calls for the test, and two calls to the method, ends up running four tests.
The solution is to either use [Parameters] meta-tag which specifies the data to use for the whole test class, or use the dataProvider for each test method, but not both with the same data at the same time.
You're missing the static reference to Paramaterized, as shown here:
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class MyTestNGTest
{
private var foo:Parameterized;
...
Basically, that error means that the [Runner] defined isn't available at runtime, which occurs if there is no static reference in the class to cause it to get linked in.
In FlexUnit 4.5.1, this approach changed to using [Rule]'s like so:
public class MyTestNGTest
{
[Rule]
public function paramaterizedRule:ParamaterizedRule = new ParamaterizedRule();
...
}
However, I can't seem to see an actual implementation of IMethodRule for paramaterized tests (that example is fictional).
I've been reading through the internals chapter in the Symfony2 docs and it says if I add a listener to the kernel.controller event I can swap the controller that gets run, I've got something that works a bit like this:
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
$replacementControllerName = .... //Some logic to work out the name of the new controller
$replacementController = ?? //Not sure what goes here
$event->setController($replacementController);
}
The bit I'm unsure if is once I've worked out the name of the replacement controller, how do I get an instance of it that I can pass to setController?
You can set your controller to any callable, which means something like
A static method array('class', 'method')
An instance method array($instance, 'method')
An anonymous function function() { ... }
A regular global function 'function';
An instance of a class implementing the __invoke() method new MyClassImplementingInvoke()
The special syntax 'class::method' which forces the ControllerResolver to create a new instance of class (calling the constructor without any argument) and returning a callable array($instanceOfClass, 'method')
EDIT:
I looked up the wrong ControllerResolver. When running Symfony in a standard setup it'll use the Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver (and not the Symfony\Component\HttpKernel\Controller\ControllerResolver). So the controller name will be handled a little bit different to what I wrote above.
The following example sums up all the possible options you have when setting your controller.
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
// call method in Controller class in YourBundle
$replacementController = 'YourBundle:Controller:method';
// call method in service (which is a service registered in the DIC)
$replacementController = 'service:method';
// call method on an instance of Class (created by calling the constructor without any argument)
$replacementController = 'Class::method';
// call method on Class statically (static method)
$replacementController = array('Class', 'method');
// call method on $controller
$controller = new YourController(1, 2, 3);
$replacementController = array($controller, 'method');
// call __invoke on $controller
$replacementController = new YourController(1, 2, 3);
$event->setController($replacementController);
}