Question

I have a view for which I'd like to mock the Show behaviour.

Authentication dialog

Once the credentials have been entered, the [Connecter] button enables itself, and then the user can click. I wish I could reproduce this behaviour without having to show the view and actually really enter my credentials.

The application is a WinForms MDI presented by the IApplicationPresenter. The IApplicationPresenter raises the ShowView to which the IApplicationView subscribed.

Then, when the IApplicationView.Shown, the IApplicationPresenter forces the user to authenticate like this:

IApplicationPresenter.OnViewShown

public void OnViewShown() { forceAuthentication(); }

private void forceAuthentication() {
    IAuthenticationView authView = new AuthenticationView();
    IAuthenticationPrenseter authPresenter = new AuthenticationPresenter();
    authPresenter.ShowView();
}

It's like I can smell one thing.

  1. It's just like I could inject the IAuthenticationView to the IApplicationPresenter. Then, this would allow me to inject my mocked view to it, and avoid the view being actually shown, which is in fact what I want to come up with. Is it the best way to make it?

Now, I want to test that when the IApplicationView is shown, the IApplicationPresenter is notified and forces authentication.

Any thoughts of a better approach in terms of mocking here?

UPDATE

IView

public interface IView {
    void CloseView();
    void SetTitle(string title);
    void ShowView();
    void RaiseVoidEvent(VoidEventHandler @event);

    event VoidEventHandler OnViewInitialize;
    event VoidEventHandler OnViewShown;
}

IApplicationView

public interface IApplicationView : IView {
    void OnUserAuthenticated();

    event VoidEventHandler ManageRequestsClicked;
}

IPresenter

public interface IPresenter<V> where V : IView {
    V View { get; }
    IDatabaseUser CurrentUser { get; }

    void CloseView();
    void OnViewInitialize();
    void RaiseVoidEvent(VoidEventHandler @event);
    void ShowView();

    event VoidEventHandler OnCloseView;
    event VoidEventHandler OnShowView;
}

Presenter

public abstract class Presenter<V> : IPresenter<V> where V : IView {
    public Presenter(V view) {
        if (view == null) throw new ArgumentNullException("view");

        View = view;
        View.OnViewInitialize += OnViewInitialize;

        OnCloseView += View.CloseView;            
        OnShowView += View.ShowView;
    }

    public virtual IDatabaseUser CurrentUser { get; protected set; }
    public virtual V View { get; private set; }

    public virtual void CloseView() { RaiseVoidEvent(OnCloseView); }
    public virtual void OnViewInitialize() { }
    public void RaiseVoidEvent(VoidEventHandler @event) { if (@event != null) @event(); }
    public virtual void ShowView() { RaiseVoidEvent(OnShowView); }

    public virtual event VoidEventHandler OnCloseView;
    public virtual event VoidEventHandler OnShowView;
}

IApplicationPresenter

public interface IApplicationPresenter : IPresenter<IApplicationView> {
    IAuthenticationPresenter AuthenticationPresenter { get; set; }

    void OnManageRequestsClicked();
    void OnUserAuthenticated(UserAuthenticatedEventArgs e);
    void OnViewShown();
}

ApplicationPresenter

public class ApplicationPresenter : Presenter<IApplicationView>, IApplicationPresenter {
    public ApplicationPresenter(IApplicationView view) : this(view, null) { }
    public ApplicationPresenter(IApplicationView view, IAuthenticationPresenter authPresenter) : base(view) {
        AuthenticationPresenter = authPresenter;            
        View.OnViewShown += OnViewShown;
        View.ManageRequestsClicked += OnManageRequestsClicked;
    }

    public IAuthenticationPresenter AuthenticationPresenter { get { return authenticationPresenter; } set { setAuthenticationPresenter(value); } }

    public void OnManageRequestsClicked() {
        var requests = new GestionDemandeAccesInformationForm();
        requests.Database = database;
        requests.MdiParent = (System.Windows.Forms.Form)View;
        requests.Show();
    }

    public void OnUserAuthenticated(UserAuthenticatedEventArgs e) { 
        CurrentUser = new DatabaseUser(e.Login, e.Password, e.DatabaseInstance);
        database = new DatabaseSessionFactory(CurrentUser);
        setAppTitle();
        showRequestsManagementView();
    }

    public void OnViewShown() { forceAuthentication(); }
}

IAuthenticationView

public interface IAuthenticationView : IView {
    string ErrorMessage { get; set; }
    string Instance { get; set; }
    IEnumerable<string> Instances { get; set; }
    string Login { get; set; }
    string Password { get; set; }

    void EnableConnectButton(bool enabled);
    void SetDefaultInstance(string defaultInstance);
    void RaiseSelectionChangedEvent(SelectionChangedEventHandler @event, SelectionChangedEventArgs e);

    event VoidEventHandler OnConnect;
    event SelectionChangedEventHandler OnDatabaseInstanceChanged;
    event VoidEventHandler OnLoginChanged;
    event VoidEventHandler OnPasswordChanged;
}

IAuthenticationPresenter

public interface IAuthenticationPresenter : IValidatablePresenter, IPresenter<IAuthenticationView> {
    void OnConnect();
    void OnViewDatabaseInstanceChanged(SelectionChangedEventArgs e);
    void OnViewLoginChanged();
    void OnViewPasswordChanged();
    void RaiseUserAuthenticatedEvent(UserAuthenticatedEventArgs e);
    event UserAuthenticatedEventHandler UserAuthenticated;
}

AuthenticationPresenter

public class AuthenticationPresenter : Presenter<IAuthenticationView>, IAuthenticationPresenter {
    public AuthenticationPresenter(IAuthenticationView view, IMembershipService service) : base(view) {
        MembershipService = service;
        View.ErrorMessage = null;
        View.SetTitle(ViewTitle);
        subscribeToEvents();
    }

    public bool IsValid { get { return credentialsEntered(); } }
    public IMembershipService MembershipService { get; set; }

    public virtual void OnConnect() {
        if (noDatabaseInstanceSelected()) display(MissingInstanceErrorMessage);
        else if (noLoginEntered()) display(MissingLoginErrorMessage);
        else if (noPasswordEntered()) display(MissingPasswordErrorMessage);
        else {
            display(EverythingIsFine);
            if (isAuthenticUser()) notifyTheApplicationThatTheUserIsAuthentic();
            else { display(InvalidLoginOrPasswordErrorMessage); }
        }
    }

    public override void OnViewInitialize() {
        base.OnViewInitialize();
        View.ErrorMessage = null;
        View.Instances = Configuration.DatabaseInstances;
        View.SetDefaultInstance(Configuration.DefaultInstance);
    }

    public void OnViewDatabaseInstanceChanged(SelectionChangedEventArgs e) { View.Instance = (string)e.Selected; }
    public void OnViewLoginChanged() { View.EnableConnectButton(IsValid); }
    public void OnViewPasswordChanged() { View.EnableConnectButton(IsValid); }
    public void RaiseUserAuthenticatedEvent(UserAuthenticatedEventArgs e) { if (UserAuthenticated != null) UserAuthenticated(e); }

    public event UserAuthenticatedEventHandler UserAuthenticated;
}
Was it helpful?

Solution

If I were you, I'd inject a factory for creating AuthenticationPresenter and in your test I would call OnViewShown() and verify on your mock (of the presenter returned by the factory) that ShowView is called.

EDIT Note that I haven't compiled this, I don't have a C# compiler right now.

Here is my version of the test. Based on my interpretation of what you really want to test :

[TestClass]
public class ApplicationPresenterTests 
{
    [TestClass]
    public class OnViewShown : ApplicationPresenterTests 
    {
        [TestMethod]
        public void ForceAuthentication() 
        {
            // given
            var authenticationPresenterFactory = new Mock<IAuthenticationPresenterFactory>();
            var authenticationPresenter = new Mock<IAuthenticationPresenter>();
            authenticationPresenterFactory.Setup(f => f.create()).Returns(authenticationPresenter.Object);
            var presenter = new ApplicationPresenter(authenticationPresenterFactory);

            // when
            presenter.OnViewShown();

            // then
            authenticationPresenter.Verify(p => p.ShowView());
        }
}

OTHER TIPS

So far, I have come up with this solution which works flawlessly.

It's all about setting up the mock object to work as expected.

[TestClass]
public abstract class ApplicationPresenterTests {
    [TestClass]
    public class OnViewShown : ApplicationPresenterTests {
        [TestMethod]
        public void ForceAuthentication() {
            // arrange

            // act
            Presenter.OnViewShown();
            var actual = Presenter.CurrentUser;

            // assert
            Assert.IsNotNull(actual);
            Assert.IsInstanceOfType(actual, typeof(IDatabaseUser));
        }
    }

    [TestInitialize]
    public void ApplicationMainPresenterSetUp() {
        Mock<IAuthenticationView> authView = new Mock<IAuthenticationView>(MockBehavior.Strict);
        authView.SetupProperty(v => v.ErrorMessage);
        authView.SetupGet(v => v.Instance).Returns(RandomValues.RandomString());
        authView.SetupGet(v => v.Login).Returns(RandomValues.RandomString());
        authView.SetupGet(v => v.Password).Returns(RandomValues.RandomString());
        authView.Setup(v => v.CloseView());
        authView.Setup(v => v.SetTitle(It.IsAny<string>()));
        authView.Setup(v => v.ShowView()).Raises(v => v.OnConnect += null).Verifiable();

        Mock<IMembershipService> authService = new Mock<IMembershipService>(MockBehavior.Loose);
        authService.Setup(s => s.AuthenticateUser(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);

        IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView.Object, authService.Object);

        ApplicationView = new ApplicationForm();

        Presenter = new ApplicationPresenter(ApplicationView, authPresenter);
    }

    protected IApplicationView ApplicationView { get; private set; }
    protected IApplicationPresenter Presenter { get; private set; }
}

Therefore, the key change was to inject the dependancy of an IAuthenticationPresenter into the IApplicationPresenter, hence the ApplicationPresenter constructor overload.

Though this has solved my problem, I better understand the need for a PresenterFactory being injected into the ApplicationPresenter, since this is the presenter which handles everything in the application, that is, the calls to other views for each which has its own presenter.

Before me lies an even more complex challenge to take on. Stay tuned!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top