A new version of OJMoq has landed in OJTest. OJMoq has changed significantly under the hood and there are some API changes. OJMoq 2 is, however, backwards compatible and you'll be able to use your current OJMoq tests inside of OJMoq 2.

Quick History

OJMoq was started as a simple class that did one thing: verify that a selector was called on an object. It was used as a little utility that was meant to get one of my tests passing in a particular Cappuccino project. It worked out pretty well and OJMoq grew as I and others need more utility out of it. Eventually, OJMoq became a huge "God" object that was really three different things: a mock, a stub and a spy.

OJMoq started to have weird edge cases that would creep in as bugs because we did not account for that particular behavior. Then, the entangled state management (whether it was a spy or a mock or a stub) caused unforeseen side effects far away from any potential change. It was time to deal with this tangled mess. The plan was to separate the three 'modes' into three classes.

It was actually surprisingly easy. Other than the three states of the OJMoq object, we have a relatively good design for OJMoq. Much of the difficult work is done in OJMoqAssert and OJMoqSelector. Those two classes haven't changed. Separating the classes was then just a matter of test driving how we wanted them to work and picking logic out of the existing OJMoq. Only OJMoqSpy has any logic that can be considered "new."

API Changes

The best way to continue forward was to break the unified OJMoq interface into three distinct classes. Let's briefly explore the new API.

    var aMock = mock(aBaseObject);
    [aMock selector:times:]
    [aMock selector:times:arguments:]
    [aMock selector:returns:]
    [aMock selector:returns:arguments:]
    [aMock selector:callback:]
    [aMock selector:callback:arguments:]
    [aMock verifyThatAllExpectationsHaveBeenMet]

For the OJMoqMock, not much has changed from the first API. We still have the same selectors and the moq(baseObject) function became mock(baseObject). The difference, however, is in the behavior. Mocks do not accept selectors that are not owned by either itself or the base object. Mocks return null for all calls by default. None of the calls are ever made on the base object. Mock, as it turns out, is just a more strict version of Moq. Next, we have the stub.

    var aStub = stub();
    [stub canPassAnything];

A stub is a very simple concept. It is a small, benign object that has absolutely no side effects. It will eat any call that is made to it. It's very flexible. There is no API to set return values or expectations. Very simply, stubs do nothing. Finally, we have the spy.

    var baseObject = something;
    var aSpy = spy(baseObject);
    [aSpy selector:times:]
    [aSpy selector:times:arguments:]
    
    [baseObject someSpiedSelector]
    
    [aSpy verifyThatAllExpectationsHaveBeenMet];

The spy is the most changed from the old API. A spy in the new OJMoq will watch for calls on the baseObject and then capture those calls. A spy is useful for when you want the side effects to happen but still want to verify some behavior by the caller. This was certainly possible in the old API but it was much more confusing and difficult to truly grok. The new separation between spy and base object allow for us to conceptually think of the spy as an unseen watcher.

Opinionated Code

You've probably noticed that I've actually reduced features! Before, you were able to use a spy as a mock, a mock as a stub and a stub as a mock. We were able to go between them rather fluidly. However, that makes it more difficult for someone to read and understand. Instead, we focus the different classes of objects on what they do best.

I think that this is the best method going forward. The old API still exists and you are welcome to use that (it's not even deprecated.. not until I'm sure that people like the new API). However, if it all goes according to plan, developers can create a testing vocabulary that is reinforced by OJMoq that will enhance communication and understanding.

The new version of OJMoq is certainly opinionated and some may disagree with that opinion. That's both fine and great. I encourage anyone that this pisses off to make a different mocking framework for Objective-J. We need all of the frameworks we can get!