Fit code, part 5 - ActionFixture
Wow - this one is a lot cleaner than I expected. I had tried overriding the C# version and had all kinds of grief. This version is straightforward and extensible.
The class has three fields:
- cells - a Parse
- actor - a Fixture
- empty - an array of Class
Cells holds the list of cells for this row. It's used by the action methods (such as enter()) to pull out data from the row.
Actor holds the object created by a start() action. Notice that it is static - that is what lets separate ActionFixture tables keep working with the same object without repeating start in every table.
Empty is the easiest - it's just an empty list so that things that want a list of argument types can have one. I marked it final, since it's never changed.
The first method is doCells(). It saves off the cells, so other methods have access to this row's Parse. Then it looks up the method in the first cell, and invokes it. (This method will be one of "start," "press," "enter," or "check.")
The fixture invokes the method on itself by "getClass().getMethod()" - looking for the method on itself. This is a place where the Java version is nicer than the C# one. The C# version hard-coded that line to the equivalent of "ActionFixture.class.getMethod()". That meant that a subclass of ActionFixture would only have access to the four methods ("start" etc.) originally planned. The Java version lets you extend this vocabulary easily.
Another thing to notice is that the fixture calls getMethod() on cells.text(), not camel(cells.text()). That's a pity - my extended vocabulary has to be spelled exactly. (I don't think the rules for camel casing are consistent throughout. I'm probably getting hung up from Fitnesse experience - I think it may have slightly different rules.)
Start() is straightforward. It creates an object of the named type, and stashes it in the actor field. I note that it doesn't camel-case its argument, so "start MyFrameObject" is different from "start my frame object". (The latter won't work.)
Enter() looks on the actor for a one-argument method it can use as a setter. It creates a TypeAdapter, which knows how to parse objects, passing it the cell text. Then it invokes the setter.
Press() invokes the named 0-argument method on the actor.
Check() assumes its 0-argument method is a getter, fetches the result, and passes it to Fixture's check() routine, which does the comparison and cell coloring.
The two variants of method() try to find an n-argument method on the actor The simple form camel-cases, so the fields on the start object can have the more user-friendly form. ("start MyFrameObject // press the rightmost button".)
It double-checks that there is only one possible method. (So, if "firstName(int)" and "firstName(String)" both exist, it will report that it doesn't know which to use.)
Next time, I'll take up TypeAdapter.