问题
I have a view for which I'd like to mock the Show
behaviour.
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.
- It's just like I could inject the
IAuthenticationView
to theIApplicationPresenter
. 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;
}
回答1:
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());
}
}
回答2:
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!
来源:https://stackoverflow.com/questions/22669972/how-to-moq-this-view