This is Part IV of a multi-part series. Below are the links to other parts of this tutorial!

In my previous articles, I have brought you up to speed with writing basic tests for basic methods. You are now able to use the @dataProvider annotation, generate coverage reports, and how to use a few select assertions.

So far we have written tests for simple, straight-forward methods. Maybe a call to an internal method inside the same class, even an if block thrown in for good measure, but nothing at all complex.

While this is great for learning, in the real world you will rarely come across something as simple as what you have encountered so far. What you will usually see are methods that instantiate other class objects, call methods within the same class, use statics, or have foreign object dependencies injected via parameters.

PAYMENT CLASS

Today I will showcase more advanced testing concepts using code we are all familiar with and may have used in the past: the payment processor API. Specifically for Authorize.net, but just as easily could be any processor API.

Explosions!

Authorize.net responded to our test by saying "The merchant login ID or password is invalid or the account is inactive.". Oops!

Maybe we should get valid Authorize.net credentials and plug them in to our test!

While that would certainly solve the issue, another quickly takes its place:

If you dive into the \AuthorizeNetAIM class, you will notice the complexity quickly grows - the methods call other methods, which call even more. Eventually there is even a cURL call that is what actually contacts Authorize.net's servers.

What happens if the Authorize.net servers are unavailable when you are writing and/or running your tests?

Should we allow our tests to fail and throw a red bar because Authorize.net may not be down? Or because our internet is down?

Why are we even worrying about what happens in this foreign class? We don't want to depend on an outside source that is out of our control! There must be a better way...

ENTER THE MOCK

PHPUnit comes with a very powerful feature to help us handle outside dependencies. It basically involves replacing the actual object with a fake, or 'mock', object that we fully control, removing all dependencies on outside systems or code that we really have no need to test.

In the \AuthorizeNetAIM class we know that the method ::authorizeAndCapture() brings some serious problems to our testing code - in that it pings an outside server that we neither have control over nor desire to control.

There is still a minor issue, however: how do we actually get out mocked object into the code we are testing? The code that instantiates the Authorize.net object is pretty concrete and leaves no room for interpretation, right?

$transaction = new \AuthorizeNetAIM(self::API_ID, self::TRANS_KEY);

DEPENDING ON DEPENDENCY INJECTION

There is this concept called dependency injection. It is a fancy name for something that is ultimately a very simple concept to understand.

Instead of using the new keyword in your methods, pass in the object in parameters.

The other way to replace that dependency is to provide the method with a pre-instantiated object in its parameters.

Actually, there's a third way: service container. I won't be going over a container today, but will speak about the benefits it brings to code quality and testing in the near future. For a quick rundown on what a service container is, just click here!

Instead of the impossible-to-replace object instantiation shown above, passing in the dependency with public function processPayment(\AuthorizeNetAIM $transaction, array $paymentDetails) means you can now pass in an object that will pass an is_a() check.

What exactly are the requirements of is_a()?

is_a — Checks if the object is of this class or has this class as one of its parents

Any class that extends \AuthorizeNetAIM will pass an is_a() check. That part is pretty easy. So, how would we pass an object that passes this check? It would need to pass certain requirements:

Has all the methods your code is expecting, and

Any methods that cause problems in your code (like authorizeAndCapture()) should be changed to make them safe for your tests.

Well, that sounds like simply extending the \AuthorizeNetAIM class would do the trick, right? Simply create a new class, say, \AuthorizeNetAIMFake, which overwrites all the methods and simply returns some expected value to remove any and all surprises.

That is actually not a bad idea, and in fact can easily work well for smaller codebases... but what happens when you have 5 classes you need to override like this? 10? 50? You can easily go over several hundred classes needing to be overridden. Do you really want to create, and maintain, several hundred files that do nothing more than extend another class and override all its methods? There must be a better way!

PHPUnit's Mock Helper

Taking into account the changes made to our code, our test would then look something like this:

The problem with this code is that you are still dependent on the \AuthorizeNetAIM class and all the code within its methods. We also don't want to create a blank class file to do this, for the reasons listed above. What to do?

PHPUnit to the rescue!

One of the most powerful tools available to you is the getMock() method - it allows you to create a new class that passes our two major requirements above, all on the fly. You do not need to create separate files for each class, you do not have to worry about maintaining a steadily-growing file structure.

To use it, you simply call it and pass in a few parameters, most of them optional.

This is ugly. There are 8 parameters, most of them optional, for this single method. Do you really want to have a window open all the time when you are writing tests? Of course not - what will happen is you will stop writing tests because this sucks.

getMockBuilder()

A few versions ago PHPUnit introduced a handy helper: getMockBuilder(). It is little more than a wrapper around the getMock() method above, but it provides a much more human-readable format of chained methods, making creating mocked objects a breeze.

All other methods in your mock object are also stubs, and they also return NULL.

What is great about this is that the authorizeAndCapture() method is no longer sending a request to the Authorize.net servers. Instead, it is returning a known value (NULL) every single time it is called.

Here is the kicker, though: You can now override the value returned by a stub method from within your test.

This means that you define the value return by your stub in your test, and when you run your test your code will think the value returned is normal, and act accordingly to your wishes.

A returned value can be anything - null, a string, an array, integers, other objects and even other mocked objects.

Payment.php:18 corresponds with if ($response->approved) {. $response was instantiated with $response = $transaction->authorizeAndCapture();. Using the knowledge you just gained above, you know this is because all stub methods return NULL unless otherwise overridden.

What is happened is that $response is NULL, but then we attempt to call approved from the object, which does not exist, thus the error.

We know we have to override the return value of authorizeAndCapture(), and thankfully it is fairly simple!

OVERRIDING STUB METHOD RETURN VALUES

To override the return value of a stub, you have to be introduced to 5 new PHPUnit methods:

We are stating that the $authorizeNet object expects to call one time the method authorizeAndCapture(), and it will return the value RETURN VALUE HERE!.

You start this process off by calling expects(), which accepts a single parameter: the number of times we are expecting the method to be called in our code. There are multiple options for the number method, include once(), any(), never() and a few more. The names are self-explanatory.

If we state that the method is expecting to be called one time, and it ends up never being called, or called more than once, our test will fail.

If we state it should never be called, but it is, the test will fail.

any() is a cheat that says, "I don't care if it is ever called, but if it is, here is the expected return.".

method() accepts the name of the method to override. In our case, it would correspond with the call $response = $transaction->authorizeAndCapture(); in our code.

Then we have will() which is simply wraps the important returnValue() where you actually define what value is returned. In this case, it is RETURN VALUE HERE!.

Running our test now will still fail, because authorizeAndCapture() is returning a string, when our code is expecting an object with an approved and transaction_id key. A simple shortcut for these types of objects is to use \stdClass():

WRAP IT UP

There is still much more work to be done. Simply looking at our code we know that we need to cover the scenario where $response->approved is false, and then how to handle the throw new \Exception($response->error_message); line.

However, you have now learned of the concept of mocked objects, stubbed methods, and why dependency injection is such a useful tool for testing.

Next up I will introduce mocked methods (similar but slightly different from mocked objects and stubbed methods!), catching exceptions and writing tests for ever more complex code.

Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!