The production service connects to gateway and processes payment as it would in a live environment
The test service connects to gateway and processes payment for say a dev environment with test API account settings for the payment gateway - or manual testing
Mock object represents the gateway and responds like the gateway would in order to run unit tests, it would not connect to the gateway it just mimics it.
I don't really understand the database backed mock gateway. When I think of manual testing I think of manually processing a payment for an order, in which case the test service (#2) would be used. Is it for creating payments to test managing them without requiring a test service or can we use fixtures/unit tests instead to test the same kind of things?
>> Mock requests / responses for testing sounds like a good start,
>> it might be nice to have the actual requests sent if test API keys
>> exist - depending on how hard it is to implement with the different
> Perhaps create a static variable like PaymentTest::$make_external_calls,
> which determines whether to use external APIs or not. False by default.
> I could imagine it would be frustrating if you're developing/running tests
> without an internet connection.
The best way of doing this is to create a class that is a really, really thin layer around the payment gateway. It might simply be the RestfulService class. Perhaps it's assigned to $this->gateway inside the Payment class. Then you have a setter, constructor argument, or Dependency Injection framework to specify what object should be provided to this gateway. In different parts of your application you can then pass one of 3 implementations:
1. The production service
2. The test service (but still connecting to the
3. A mock object that doesn't call anything
You don't necessarily need to have 3 separate classes - the first two might be the same class but with different properties.
If you like, you can use Phockito (or PHPUnit built-in mocking system, which I hate because of its verbosity) to create the 3rd one. Or you can hand-roll a mock object that does what your tests need it to.
#1 would be used for production deployment
#2 would be used for manual test environments and possibly automated integration testing
#3 would be used for unit tests
You might have a 4th environment, that provides a database-backed mock gateway, where that database back-end is exposed in a modeladmin. This would let you create a self-contained manual testing environment, but it means that you're testing more code that's not actually part of the production system, and it's more work. It's arguable as to whether or not this is worthwhile.
The point is that by using this dependency injection (even without a DI framework this is still dependency injection) you don't need to bake any knowledge of this stuff into your Payment class. It makes your code more testable, more maintainable, and more flexible.