During the prep for the Test Double talk I gave at Symfony Live, I read through the paper Evolving an Embedded Domain-Specific Language in Java and it really struck a chord with me for two reasons. Firstly, it gave me further insight in to the theory behind my talk and secondly, because it detailed a process I was planning on going through in the not too distant future. The paper essentially details the author's findings as they studied the API for various tools before designing and build the DSL for jMock.

The main thing I took from the paper was the use of interfaces to limit the available choices while building up "train wreck" style set of method calls.

As an example, take a look at this screenshot from PhpStorm using the master branch of mockery:

The shouldHaveReceived method returns a Mockery\VerificationDirector (which extends Mockery\Expectation for implementers convenience). Calling with on the Mockery\VerificationDirector actually returns the same Mockery\VerificationDirector at which point you could call one of the many other methods, including another call to with.

Ladies and Gentleman, this does not make sense. It also makes for a very poor API experience. Of course you're not likely to write $spy->shouldHaveReceived("foo")->with("bar")->once()->with("bar") and you could argue that the implementation should prevent this crazyness, but I'm sure you get the point.

For the previously mentioned new TestDouble API for mockery, I decided to have a crack at using interfaces to define the syntax for the DSL, in a way that I think is most suitable for creating readable tests. That way would be (for a spy), specifying the method expected to have been called, (optionally) specifying the arguments it should have received and then (optionally) specifying the number of times you expected the call to happen.

interface Spy { /** * @return CallArgumentVerifier */ function shouldHaveReceived($method); }

The spy interface makes it clear that calling the shouldHaveReceived method satisfies the first part of our desired syntax and will give you a CallArgumentVerifier. Now the CallArgumentVerifier needs to make available a set of methods to specify the arguments the call received and also allow the call chain to continue. To do so, all of the with* methods will return a CallCountVerifier. Simple right? But remember, specifying the arguments is an optional step, so to facilitate this, we have the CallArgumentVerifier extend a CallCountVerifier, allowing the client to skip straight to the call count verification methods. Cascading the segregated interfaces in this way let's us force our particular narrative on the users.

interface CallArgumentVerifier extends CallCountVerifier { /** * @param mixed $arg The first expected argument * @param mixed $arg,... The subsequent expected arguments * * @return CallCountVerifier */ function with($arg/*, $arg2..., $arg3...*/); /** * @param array $args The expected arguments * * @return CallCountVerifier */ function withArgs(array $args); /** * @return CallCountVerifier */ function withNoArgs(); /** * @return CallCountVerifier */ function withAnyArgs(); }

The methods available on the CallCountVerifier should be the last stop in our train-wreck call chain, so we simply have them return void.

interface CallCountVerifier { /** * @return void */ function once(); /** * @return void */ function twice(); /** * @param int $count * * @return void */ function times($count); }

And that's pretty much it! This spy example is quite simple, but it has a nice effect when plugged in to your favourite IDE. The DSL for Mocks will have a deeper and wider API, so might be more complicated.

As previously mentioned, I've been pairing on this with @karptonite, so would like to thank him for his help. I've no idea how this will pan out once we start trying to build the implementation, but I'm pleased with the progress so far.