Assert that a Mock Facade was called/run in Laravel 4 - php

I am beginning to write tests using Laravel 4.2, PHPUnit, and Mockery.
How can I assert that a Mock Facade was called/run by the Controller that I am testing?
For example, I've got the following Mockery for Queue. My controller doesn't return anything that has to do with the Queue::push(), so is there a way I can assert that it triggered Queue::push()?
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
If I comment out the Queue::push() in my controller, the Queue::shouldReceive throws an exception:
Mockery\Exception\InvalidCountException: Method push() from Mockery_0_Illuminate_Queue_QueueManager should be called exactly 1
times but called 0 times.
Is the only way to assert that the Mock Facade ran is to do something like the following?
$queuepushran = false;
try {
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
$queuepushran = true;
} catch (\Mockery\Exception\InvalidCountException $e) {
//Queue threw an exception
$queuepushran = false;
}
$this->assertTrue($queuepushran);

You're on the right track with using mock objects and expectations to test this, but this isn't quite how mocks should be used. Your code snippet seems to be missing actually calling the controller, so it's hard to tell exactly what's going on, but I'll just run over a full example.
You want to test that when your controller method runs, the Queue facade's push() and connected() methods are called once each. What we want to do is use Laravel's cool facade functionality to replace the real QueueManager class behind the Queue facade with a mock one, that way when the controller calls Queue::push(..), it will be calling our mock object instead.
So, lets start building the test:
public function testSomeControllerMethodCallsTheQueue()
{
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
}
We've now swapped out the real QueueManager for our mock one, and set up how we expect them to be called. Since each test should only be doing one thing, we've now done all we need to do to 'arrange' the test.
Next, we need to 'act' by calling the controller.
public function testSomeControllerMethodCallsTheQueue()
{
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
$this->action('GET', 'YourController#method');
}
The last step is to 'assert', but we don't actually need to do anything. If our Mockery expectations are met, the test will complete and go green. If our Mockery expectations are not met, an exception will be thrown and the test will go red. The exceptions that Mockery throws are intended to be what fails your test, you don't need to do assertions like when comparing the return value or response with what you expect -- Mockery handles all that.

Related

How to test multiple PHP TypeErrors in one test?

I have following method:
MyClass {
public function __construct(array $data, ?SomeObject $object): void
}
I have following test:
public function testWrongDataTypeThrowsErrorException(): void
{
$this->expectException(TypeError::class);
//works as expected
new MyClass(null, new SomeObject);
//this is never called
new MyClass('string', new SomeObject);
}
And what happens is it only runs 1 test/assertion with the null parameter. The second time when I try to create MyClass with string, it never gets there. Why is that?
The same logic works if the code throws regular Exception.
I can write 2 separate methods instead, but thats very inconvenient.
Or is it stupid to test for this? My idea is that I want to test that the method accept array and only array. So if somebody would remove it from the method the test would fail.
Place the expectExeception... or exepectError... only directly before the action you want to assert it for.
If you need multiple, write multiple tests methods - for each exception and error individually.
The suggestion above is how it works with Phpunit out of the box and normally keeps your tests clean.
There is however also the option to just make use of PHP here and handle it like the following:
try {
new MyClass(null, new SomeObject);
$this->fail('an expected exception was not thrown');
} catch (TypeError $e) {
$this->addToAssertionCount(1);
}
Then the test-method is not interrupted and you can continue (you have to remove the earlier TypeError expectation as Phpunit will not detect it any longer).
If you feel this is more convenient than separated tests, go for it.
You can even add a helper function and pass a closure in so you can wrap it. Normally it's better to stick with clean tests IMHO, if you really must test for that.
From what you write in your question, I'd say the problem is solved in PHP since the array type-hint back in PHP 5.??? and I would not need to test for that (or you would also need to write tests for your tests, because also the tests can be changed).
If someone changes the method signature, they should have a valid reason for that. If they make a mistake, they perhaps didn't cover their refactoring with tests so the problem lies not in the code of the unit and is not to test for with unit tests.
Keep in mind, that a test should only have one reason to fail. So it's good to aim for that when writing a (unit) test.
Is it OK to have multiple asserts in a single unit test?

Mock an Exception in phpUnit

The method I am trying to test has a try catch like
try {
$fooClass->doStuff(); // Throws \Lib\Custom\Exception
}
catch (\Lib\Custom\Exception $exception) {
return false;
}
I want to test if the return is false, but the custom exception is not loaded when my tests are executed.
Php unit has the option of mocking classes, but I can't seem to use this for Exceptions.
$exceptionMock= $this->getMockBuilder(\Lib\Custom\Exception::class)->getMock();
$fooClassMock = $this->getMockBuilder(fooClass::class)->getMock()
->method('doStuff')
->willThrowException($exceptionMock);
Gives me the folowing exception:
Argument 1 passed to
PHPUnit_Framework_MockObject_Builder_InvocationMocker::willThrowException()
must be an instance of Exception, instance of Mock_Exception_c4dd9394 given
How to properly mock this Exception to test the function?
I don't think you need to mock the exception.
why not try this?
$fooClassMock = $this->getMockBuilder(fooClass::class)->getMock()
->method('doStuff')
->willThrowException(new \Lib\Custom\Exception());
Or something similar..
The reason that your test isn't working is because the class isn't known in the test. When that happens and you create a mock in PHPUnit, it does some trickery to create a class definition and then extends that to create the mock. Otherwise it would use the extend the class itself and you would not have had a problem.
In this case, PHPUnit creates a fake \Lib\Custom\Exception and then uses that to create the mock. The fake class created doesn't extend anything (since it wouldn't know what to extend/implement). So the type hint in the willThrowException will not be matched because your mock object didn't extend Exception.
This would happen with any type hinting for extended classes in PHPUnit if the class is not loaded in the test. In order to fix this, you need to have the class available in the test via a require, include, or autoloader.

Correct unit testing

I started using unit and functionality test with this project and because of this I have some questions:
Im working with the symfony php framework. And I have a doctrine like LDAP ORM service.
Furthermore I have a user repository (as a service) which depends on the LDAP ORM service, the logger and a validation service.
Now I want to write a unit test for the addUser function of the UserRepo.
It will internally call: getNewUidNumber, userToEntities, doesUserExist and getUserByUid.
My question is:
Should I mock all these internal function to just test the addUser function? Would that be against the unit test idea (just test the API).
Or should I just mock the LDAP ORM service, the Logger, and the validation service, so that the class calls all internal functions? But this would cause a huge test function with a lot of mocking because I have to mock the repositories for all internal repositories calls.
Or should I start the symfony kernel and use the ServiceContainer to use the ORM LDAP service with a real test database. But wouldn't that be a functionally test and not a unit test?
I heard that its bad to have so many dependencies in a test. So I thought it would be bad to use the whole serviceContainer.
Adduser:
public function addUser(User $user)
{
$pbnlAccount = $this->userToEntities($user);
if(!$this->doesUserExist($user)) {
$pbnlAccount->setUidNumber($this->getNewUidNumber());
$this->ldapEntityManager->persist($pbnlAccount);
$this->ldapEntityManager->flush();
}
else {
throw new UserAlreadyExistException("The user ".$user->getUid()." already exists.");
}
return $this->getUserByUid($user->getUid());
}
For more code, like the internal functions:
https://gist.github.com/NKPmedia/4a6ee55b6bb96e8af409debd98950678
Thanks
Paul
First, I would like to rewrite the method a tiny bit, if I may.
public function addUser(User $user)
{
if ($this->doesUserExist($user)) {
throw new UserAlreadyExistException("The user ".$user->getUid()." already exists.");
}
// ... shortened for brevity
$pbnlAccount = $this->userToEntities($user);
$this->ldapEntityManager->persist($pbnlAccount);
}
The other relevant method is:
private function doesUserExist(User $user)
{
$users = $this->ldapRepository->findByUid($user->getUid());
return count($users) === 1;
}
Immediately we can see that we basically have two tests:
We test that the method throws when the user exists
We test that the method persists a PbnlAccount if the user does not exist.
If you do not see why we have these two tests, note that there are 2 possible "flows" in this method: one where the block inside the if statement is executed, and one where it is not executed.
Lets tackle the first one:
public function testAddUserThrowsWhenUserExistsAlready()
{
$user = new User();
$user->setUid('123');
$ldapRepositoryMock = $this->createMock(LdapRepository::class);
$ldapRepositoryMock
->method('findByUid')
->expects($this->once())
->with('123')
->willReturn(new PbnlAccount());
$userRepository = new UserRepository($ldapRepositoryMock);
$this->expectException(UserAlreadyExistException::class);
$userRepository->addUser($user);
}
The second test is left as an exercise for the reader :)
Yes you will have to do some mocking in your case. You wil need to mock the LdapRepository and LdapEntityManager both in this case.
Note 1: this code probably is not runnable, since I do not know the exact details of your code base (and I wrote this off the top of my head), but that is beside the point. The point is that you want to test for the exception.
Note 2:
I would rename your function to createNewPbnlAccountForUser(User $user) which is longer, but more descriptive of what it actually does.
Note 3:
I am not sure why you are returning $this->getUserByUid() since that seems redundant (you already have the User right there), so I am ommitting that case.
You need to mock ldapEntityManager and all repository services but not the internal function.And as you said, don't boot kernel in unit test. So, you should test all cases with success and throwing exception (make sure to check all behaviour)
If you want to perform a unit test, you should mock all collaborators.
Now, entity managers, ldap services and so on should not be mocked (read more here).
Moreover, if you happen to be in a situation where the Arrange part (set mocks, stubs, and so on) is painful and take "a lot" of the test, maybe this is a smell that your class has too many responsibility (is doing too much things).
That said, when I do unit test, I would like the test to fail only for an internal (to the class) reason, not because I've changed a collaborator line that messes up all my tests.

Mockery "shouldReceive" yet method doesn't exist

I'm trying to understand Tests and Mockery a bit more with Laravel. I have a repository pattern setup, which my controller users. I want to test my basic getAllUsers()method:
public function test_get_all_users_method()
{
$repo = Mockery::mock('Acme\Repositories\User\UserRepository');
$repo->shouldReceive('all')->once()->andReturn('foo');
$controller = new Acme\Controllers\Api\UserController($repo);
$response = $controller->getComponents();
$this->assertEquals('foo', $response);
}
As I understand it, I'm mocking my UserRepository, and I expect my UserRepository to have it's all() method hit. This returns some dummy data and I expect to see this in my response output.
So that works fine. The all() method exists in my Eloquent implementation of the repository. However, if I remove the all() method, the test still passes... Why would it? Surely the test should fail.
If this is normal, I'm struggling to understand why I'd test my controller like this, since I could pass any old method name into it even if it exists or not.
Cheers
That's how mockery operates by default, I like it that way because it allows me to develop by wishful thinking, i.e. I wish my UserRepository interface had an all method.
You can tell mockery to disallow it though, it's a bit ugly, but you can put this in your test bootstrap file:
\Mockery::getConfiguration()->allowMockingNonExistentMethods(false);
You could also set this up to control it with an environment variable or something, so you allow mocking non-existent methods during normal use, but prevent it on your continuous integration run etc.

How to use PHPUnit to test a method that calls other methods of the same class, but returns no value

How do you write a unit test for a method that calls other methods of the same class, but doesn't return a value? (Let's say with PHPUnit.)
For example, let's say that I have the following class:
class MyClass {
public function doEverything() {
$this->doA();
$this->doB();
$this->doC();
}
public function doA() {
// do something, return nothing
}
public function doB() {
// do something, return nothing
}
public function doC() {
// do something, return nothing
}
}
How would you test doEverything()?
EDIT:
I'm asking this because from what I've read it seems like pretty much every method should have its own dedicated unit test. Of course, you also have functional and integration tests, but those target specific routines, so to speak (not on a per method level necessarily).
But if pretty much every method needs its own unit test, I'm thinking it would be "best practice" to unit test all of the above methods. Yes/no?
Okay! I've figured it out! As might be expected, mocking is what I need in this situation--and mocking a sibling method is called partial mocking. There's some pretty great info about PHPUnit mocking in this article by Juan Treminio.
So to test doEverything() in the above class, I would need to do something like this:
public function testDoEverything()
{
// Any methods not specified in setMethods will execute perfectly normally,
// and any methods that ARE specified return null (or whatever you specify)
$mock = $this->getMockBuilder('\MyClass')
->setMethods(array('doA', 'doB', 'doC'))
->getMock();
// doA() should be called once
$mock->expects($this->once())
->method('doA');
// doB() should be called once
$mock->expects($this->once())
->method('doB');
// doC() should be called once
$mock->expects($this->once())
->method('doC');
// Call doEverything and see if it calls the functions like our
// above written expectations specify
$mock->doEverything();
}
That's it! Pretty easy!
BONUS: If you use Laravel and Codeception...
I'm using the Laravel Framework as well as Codeception, which made it a little bit trickier to figure out. If you use Laravel and Codeception you'll need to do a little bit more to get it working, since the Laravel autoloading doesn't by default connect into the PHPUnit tests. You'll basically need to update your unit.suite.yml to include Laravel4, as shown below:
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
class_name: UnitTester
modules:
enabled: [Asserts, UnitHelper, Laravel4]
Once you've updated your file, don't forget to call php codecept.phar build to update your configuration.
While your mocking test does achieve your goal, I would argue that you've decreased confidence in the code. Compare the original trivial method to the complicated method that tests it. The only way the method under test can fail is by forgetting to add one of the method calls or mistype a name. But you're now doubly-likely to do that with all that additional code, and it doesn't have any tests!
Rule: If your test code is more complicated than the code under test, it needs its own tests.
Given the above, you're better off finding another way to test the original code. For the method as written--three method calls with no parameters--inspection by eyeball is sufficient. But I suspect that the method does have some side-effects somewhere, otherwise you could delete it.
Unit testing is about testing the class as a unit, not each method individually. Testing each method alone is a good indication that you're writing your tests after the code. Employing Test Driven Development and writing your tests first will help you design a better class that is more-easily testable.

Categories