2

I have an AuthorizationUpdater service, with an implementation based on Doctrine ORM. While I finished the production code, and have some tests, I was forced out of my usual TDD cycle by not knowing how to write these two:

The Doctrine implementation of AuthorizationUpdater take a Doctine EntityManager in its constructor, which it uses to do a read and a write. These two tests are supposed to test that when either of these EntityManager methods throws a Doctine ORMException, it is caught by the service and converted to a domain exception.

My test is an integration test that gets the EntityManager to use from a factory.

Creating a simple fake via the PHPUnit mock API (getMock('className')->method('updateStuff')->willThrow(new SomeExcetion)) does not seem nice in this case. The service uses the EntityManager before the method the exception gets thrown from, so mocking out everything is not an option. This leaves creating some complex fake of EntityManager and partially mocking it (just the method that should throw an exception). The later requires providing the real constructor arguments, which my test actually does not have access to, as this responsibility lies in another package. Hence both approach are not satisfactory, and leave me preferring to have this ORMException catching behaviour to be not tested.

Is there a nice way to write the tests I'm after? Being able to monkey-patch the relevant method on the EntityManager instance would be prefect, though that'd require the runkit extension.

I'm using PHP 7, and might be able to switch to PHP 7.1 if needed.In case you're curious, the code is on GitHub, with the relevant tests residing at the end of DoctrineAuthorizationUpdaterTest.

Jeroen De Dauw
  • 8,671
  • 10
  • 46
  • 71
  • Our normal strategy for this is would be to elevate the access to `getDonationById` to protected, so PHPUnit can mock it (in your case to return null). We don't generally alter our code to accommodate testing, but in this case we figure it's worth it for testability, and really doesn't detriment the intent of the code. Pragmatism over dogmatism – Adam Cameron Mar 13 '16 at 07:51
  • I do not want to test what happens when this method returns null, I want to test that it (or rather the class as a whole, since the method is an implementation detail) catches the ORMException it gets. I've now also linked the exact production code I want to test. – Jeroen De Dauw Mar 14 '16 at 03:18
  • Sorry, your test makes it look like you're testing this: https://github.com/wmde/FundraisingFrontend/blob/e754ded57000ae4d1f5b66d3b11370cdadb43fcd/src/DataAccess/DoctrineAuthorizationUpdater.php#L33. OK, well it's much easier then. Just mock `entityManager->find()` to throw yer `ORMException`. Bear in mind that you can mock individual methods of `entityManager`, and indeed individual calls to its `find()` method (like the fifth one, or something). However if you're just calling `allowDonationModificationViaToken()`, then just mocking `find()` to throw the exception is fine. – Adam Cameron Mar 14 '16 at 06:36
  • That's what I would do if I was able to easily construct the EntityManager in my test. As it stands, I'm getting an instance via a factory, which itself gets it from a factory in another component. So details of the construction are hidden behind a component boundary, and I'd really rather not pollute my test with them. – Jeroen De Dauw Mar 15 '16 at 07:01
  • I think perhaps your shooting yourself in the foot by approaching logic tests at this level as "integration" tests. You're just wanting to test the logic *in that function*, so simply do that. The logic doesn't (and *shouldn't*) care how it's called. If your tests are hard to write... you need to look at your approach and indeed your end game. I know answering to "how do I do this?" with "not that way" isn't so helpful, but if confronted with your situation: that's what my position is. :-( – Adam Cameron Mar 15 '16 at 08:20

1 Answers1

1

Instead of creating a mock object through the PHPUnit mock API you could create a proxy class in your test namespace, that gets the EntityManager from the factory as constructor parameter. The proxy class passes every method call to the EntityManager, except for the calls that are to throw an exception. Most calls could be passed through with the __call magic method, except for the calls to interface methods of Doctrine\Common\Persistence\ObjectManager, which the Proxy class will probably have to implement (interface methods can't be implemented with call).

Is this proxy class the "complex fake" you are talking about and want to avoid? Then maybe you can use the Mockery library and don't have to write the class.

Community
  • 1
  • 1
chiborg
  • 23,387
  • 11
  • 88
  • 105