drewbourne / mockolate

fake chocolate, mock objects and test spies for AS3
http://mockolate.org/
MIT License
145 stars 27 forks source link

channelset.login #2

Closed ghost closed 14 years ago

ghost commented 14 years ago

Hi Drew

Is there anyway to mock a channelset.login and logout? I have the following code in my delegate called from my controller, which I would like to confirm works. I tried Brian LeGross's RPCStubs, but he confirmed that he has not yet implemented channelsets, so just wondering if this is something you deal with?

Thanks Des

Controller

public class LoginController extends AbstractController { [Inject] // Wire in the model public var loginModel:LoginModel;

    [Inject]    // Wire in the delegate for remote calls
    public var loginDelegate:LoginDelegate;

    [Dispatcher]    // Allows this class to dispatch events.
    public var eventDispatcher:IEventDispatcher;

    public function login():void {
        // Handle successful login.
        var loginResultEvent:Function = function(event:ResultEvent ):void  {
            eventDispatcher.dispatchEvent( new AuthenticationEvent (AuthenticationEvent.LOGGEDIN));
        }

        // Handle login failure.
        var loginFaultEvent:Function = function(event:FaultEvent):void {
            this.eventDispatcher.dispatchEvent(new AuthenticationEvent (AuthenticationEvent.LOGIN_FAIL));
        }
        // Log into the Remote server channelset login
        executeServiceCall(loginDelegate.login (loginModel.username,loginModel.password), loginResultEvent, loginFaultEvent);
    }

}

Delegate

public class LoginDelegate {

    private var channelSet:ChannelSet;

    public function login(username:String, password:String):AsyncToken
    {
        channelSet = ServerConfig.getChannelSet(authenticationService.destination);
        return channelSet.login(username, password);
    }

}

drewbourne commented 14 years ago

Yes you can mock a ChannelSet.

However in the current implementation of your LoginDelegate there are some issues.

1) the ChannelSet instance it uses is not public, it is retrieved from a ServiceLocator-type Object, and 2) the ServerConfig.getChannelSet() method appears to be static and cannot be mocked, 3) what/where is authenticationService on the LoginDelegate?

Issues 2, 3 can be resolved by using Dependency Injection. Instead of the LoginDelegate asking ServerConfig which ChannelSet it should use, you instead give it the instance when it is constructed.

Change LoginDelegate to have a public var for the ChannelSet that can be injected.

// LoginDelegate.as
package deshartman
{
    import mx.messaging.ChannelSet;
    import mx.rpc.AsyncToken;

    public class LoginDelegate
    {
        public var channelSet:ChannelSet;

        public function LoginDelegate()
        {
            super();
        }

        public function login(username:String, password:String):AsyncToken
        {
            return channelSet.login(username, password); 
        }
    }
}

And then when you construct the Services, Delegates, etc

var authenticationService:AuthenticationService = new AuthenticationService();

var loginDelegate:LoginDelegate = new LoginDelegate();
loginDelegate.channelSet = ServerConfig.getChannelSet(authenticationService.destination);

This decouples LoginDelegate from ServerConfig, and allows you to inject a mock ChannelSet when testing.

With the above changes it is now much easier to mock the ChannelSet.

package deshartman
{
    import mockolate.runner.MockolateRunner; MockolateRunner;

    import mx.messaging.ChannelSet;

    import org.hamcrest.object.equalTo;

    [RunWith("mockolate.runner.MockolateRunner")]
    public class LoginDelegateTest
    {
        public var delegate:LoginDelegate;

        [Mock]
        public var channelSet:ChannelSet;

        [Before]
        public function setup():void 
        {
            delegate = new LoginDelegate();
            delegate.channelSet = channelSet;
        }

        [Test]
        public function loginShouldCallServiceWithUsernameAndPasswordAndReturnAsyncToken():void 
        {
            var username:String = "USERNAME";
            var password:String = "PASSWORD";            
            var token:AsyncToken = new AsyncToken();

            mock(channelSet).method("login").args(username, password).returns(token)

            assertThat(delegate.login(username, password), equalTo(token));
        }
    }
}

If you need to mock the result or fault from the ChannelSet you can do the following:

[Test(async)]
public function loginShouldSucceed():void 
{
    var username:String = "USERNAME";
    var password:String = "PASSWORD";
    var resultData:String = "OK";
    var token:AsyncToken = new AsyncToken();
    var resultEvent:ResultEvent = new ResultEvent(ResultEvent.RESULT, false, false, resultData, token);

    mock(channelSet).method("login").args(username, password)
        .returns(token)
        .answers(new ResultAnswer(token, resultEvent))
        .dispatches(resultEvent);

    Async.proceedOnEvent(this, channelSet, ResultEvent.RESULT);

    delegate.login(username, password);
}

[Test(async)]
public function loginMayFail():void 
{
    var username:String = "USERNAME";
    var password:String = "PASSWORD";
    var faultCode:String = "DENIED";
    var faultString:String = "Bad username or password";
    var faultDetail:String = "";
    var token:AsyncToken = new AsyncToken();
    var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT, false, false, new Fault(faultCode, faultString, faultDetail), token);

    mock(channelSet).method("login").args(username, password)
        .returns(token)
        .answers(new FaultAnswer(token, faultEvent))
        .dispatches(faultEvent);

    Async.proceedOnEvent(this, channelSet, FaultEvent.FAULT);

    delegate.login(username, password);
}

HTH

ghost commented 14 years ago

Drew,

Thanks for the explanation. Helps understand the mocking even better. One part I am not clear on:

new ResultAnswer(token, resultEvent))

Not sure what this is and I cannot find this in Flex

Thanks Des

drewbourne commented 14 years ago

ResultAnswer and FaultAnswer are new additions to Mockolate to make working with AsyncToken easier. They call the result or fault responders of the AsyncToken. If you don't have them in your clone of Mockolate you might need to do a git pull or git fetch to get the latest.

If you have a look at HTTPServiceDecorator in the mockolate.decorations.rpc package, you can see in the result() and fault() methods how the token, result or fault, is managed.

HTH

ghost commented 14 years ago

Drew

That was the problem. I pulled the latest and I can now see ResultAnswer. My test works, except that I had to remove the ".dispatches(resultEvent);" part of the mock. It keeps failing with

Error: Mockolate target is not an IEventDispatcher, target: [object LoginDelegateAD6451211F1E599AFD7750EE2CD7054A1F78770D]

Not sure why.

Thanks for your help again. Des

drewbourne commented 14 years ago

Its just saying that the LoginDelegate class isn't an IEventDispatcher, so it doesn't support using dispatchesEvent(). Safe to take that call out if you don't need it to dispatch any events.

cheers, Drew

ghost commented 14 years ago

Drew

Thanks for the clarification. I have all my Mocks working now!

Des