MVP(Model-View-Presenter pattern) for test an ASP.NET Webforms Applications

Đa số website có 1 trang contact. User input thông tin và gửi request to server.Server xử lý data và trả lại result. Làm cách nào chúng ta test trang này ?

getting-started-with-testing-in-aspnet-webforms-1

Khi một chương trình được develop sử dụng MVP pattern nó có thể dễ dàng được test hơn do các layers được tách riêng rẽ .The MVP pattern nghĩa là Model-View-Presenter và không khác nhiều so với MVC (Model-View-Controller). Trách nhiệm mỗi lơp là rõ ràng hơn nên dễ dàng định nghĩa các testcase. Framework mà giúp chúng ta build WebForms application using the MVP Pattern is WebFormsMVP.

Model

Model là lớp mà keep data. Trong trường hợp này nó là một lớp với 3 properties được thể hiện ra bởi text field và một thuộc tính giữ liệu model được lưu hay không vào db

Listing 1: ContactModel.cs

1.public class ContactModel 2.{ 3.public string Name { get; set; } 4.public string Message { get; set; } 5.public enProcessState ProcessState { get; set; } 6.public string Email { get; set; } 7.}

Bao gồm 1 tham chiếu tới enProcessState enum. Listing 2: enProcessState.cs

1.public enum enProcessState 2.{ 3.Pending, 4.Saved 5.}

View

View là một user control mà displays the form và defines events tương ứng với action của user. Bởi vì một view có thể được implemented trong nhiều cách mà một an interface được defined và user control is implementing that interface.

Listing 3: IContactView.cs

01.public interface IContactView : IView<ContactModel> 02.{ 03.event EventHandler<ContactEventArgs> Contacting; 04.} 05.public class ContactEventArgs : EventArgs 06.{ 07.public string Name { get; set; } 08.public string Email { get; set; } 09.public string Message { get; set; } 10.}

Đây là user control mà implement giao diện trên

Listing 4: ContactControl.ascx

01.<asp:MultiView runat="server" ID="mv" ActiveViewIndex="0"> 02.<asp:View runat="server" ID="vContact"> 03.<fieldset> 04.<legend>Contact Us</legend> 05.<table> 06.<tr> 07.<td> 08.Name 09.</td> 10.<td> 11.<asp:TextBox runat="server" ID="txtName" /> 12.<asp:RequiredFieldValidator ErrorMessage="*" ControlToValidate="txtName" runat="server" /> 13.</td> 14.</tr> 15.<tr> 16.<td> 17.Email 18.</td> 19.<td> 20.<asp:TextBox runat="server" ID="txtEmail" /> 21.</td> 22.</tr> 23.<tr> 24.<td> 25.Message 26.</td> 27.<td> 28.<asp:TextBox runat="server" ID="txtMessage" TextMode="MultiLine" Rows="10" /> 29.</td> 30.</tr> 31.<tr> 32.<td colspan="2"> 33.<asp:Button Text="Contact Us" runat="server" OnClick="btnContactUs_Click" /> 34.</td> 35.</tr> 36.</table> 37.</fieldset> 38.</asp:View> 39.<asp:View runat="server" ID="vSaved"> 40.Contact request was Saved! <a href="ContactList.aspx">View all</a> contact requests. 41.</asp:View> 42.</asp:MultiView>

Và code behind

Listing 5: ContactControl.ascx.cs

01.public partial class ContactControl : MvpUserControl<ContactModel>, IContactView 02.{ 03.public event EventHandler<ContactEventArgs> Contacting; 04.private void OnContacting(ContactEventArgs args) 05.{ 06.if (Contacting != null) 07.Contacting(this, args); 08.} 09.protected void btnContactUs_Click(object sender, EventArgs e) 10.{ 11.ContactEventArgs args = new ContactEventArgs(); 12.args.Name = txtName.Text; 13.args.Email = txtEmail.Text; 14.args.Message = txtMessage.Text; 15.OnContacting(args); 16.View activeView = Model.ProcessState == enProcessState.Saved ? vSaved : vContact; 17.mv.SetActiveView(activeView); 18.} 19.}

Presenter

The presenter is chịu trách nhiệm cho việc passing the model to the view and đăng ký event cho view đó để thi hành action tương ứng.

Listing 6: ContactPresenter.cs

01.public class ContactPresenter : Presenter<IContactView> 02.{ 03.public ContactPresenter(IContactView view) 04.: base(view) 05.{ 06.view.Contacting += new EventHandler<ContactEventArgs>(view_Contacting); 07.} 08.void view_Contacting(object sender, ContactEventArgs e) 09.{ 10.if (View.Model.ProcessState == Views.Models.enProcessState.Saved) 11.return; 12.View.Model.Name = e.Name; 13.View.Model.Email = e.Email; 14.View.Model.Message = e.Message; 15.View.Model.ProcessState = Views.Models.enProcessState.Saved; 16.} 17.}

Project Structure

Project Structure in Visual StudioProject Structure in Visual Studio

Unit Testing

Chúng ta add a Test Project cho sulution. Cần add những reference sau đây to our Test Project:

System.Web System.Web.Abstractions WebFormsMvp Chúng ta cũng cần add một reference to our Logic Project.

Mô phỏng HttpContext

Vấn đề lớn nhất khi kiểm tra một trang web là bạn phải để mô phỏng các yêu cầu từ khách hàng và những thứ khác như nếu người dùng được xác thực, vv

Đây là nơi mà một mock-up framework đang giúp chúng ta tạo ra các đối tượng hoặc giao diện với bất kỳ loại hành vi.

Chúng ta sẽ sử dụng Rhino.Mocks và để làm điều đó, chúng ta sẽ cài đặt các gói tương ứng từ NuGet.

In Package Manager Console,cài đặt gói-RhinoMocks..

Bây giờ chúng ta có có đối tượng HTTPCONTEXT để test

Listing 7: Simulating the HttpContext along with options like if the user is authenticated etc.

01.public HttpContextBase MockContext(bool isAuthenticated = false, string username = "") 02.{ 03.//Creating the HttpContext object 04.var httpContext = MockRepository.GenerateMock<HttpContextBase>(); 05.//Creating the Identity object 06.var identity = MockRepository.GenerateMock<IIdentity>(); 07.identity.Stub(u => u.Name).Return(username); 08.identity.Stub(i => i.IsAuthenticated).Return(isAuthenticated); 09.//Creating the User Object 10.var user = MockRepository.GenerateMock<IPrincipal>(); 11.user.Stub(u => u.Identity).Return(identity); 12.httpContext.Stub(ctx => ctx.User).Return(user); 13.return httpContext; 14.}

Code trên tạo ra mocked HttpContext, sets whether the user is authenticated and the username of the current user.

Cho nên nếu chúng ta muốn test code mà check xem authenticated thì cho dù chúng ta ko có 1 request thực sự thì Context.User.Identity.IsAuthenticated sẽ work as expected.

Khi tạo unitest thì sẽ follow the AAA pattern. AAA stands for Arrange, Act and Assert. PHương thức trên mà tạo ra HttpContext là thuộc về phần Arrange của chương trình unitest.Phần này cũng bao gồm cả View and Presenter .

Arrange

So let's create those two:

Listing 8: Creating the View and the Presenter inside our Unit Test

1.//First create the view 2._View = MockRepository.GenerateStub<IContactView>(); 3.//Then lets create our Presenter 4._Presenter = new ContactPresenter(_View, new ContactRepository()); 5._Presenter.HttpContext = MockContext(); View có thể là WPF UserControl, a Silverlight UserControl etc. Testing if the view actually works falls into the area of Acceptance Tests.

Act

The Act part được mô tả dưới đây

Listing 9: Act part of our Unit Test

1.ContactEventArgs args = new ContactEventArgs() 2.{ 3.Name = "John Katsiotis", 4.Email = "[email protected]", 5.Message = "Please contact me!" 6.}; 7._View.Raise(v => v.Contacting += null, _View, args); Đầu tiên tạo model and set giá trị thích hợp theo case test. Sau đó set model cho view (Đã tạo ra trong Arrange part) tiếp theo sử dụng method ma được defined bởi RhinoMocks Framework và cho phép raise lên Contacting event of view.

Điều chúng ta expect là the presenter đăng ký event, tạo ra contact và thay đổi ProcessState property của model.

Assert

Là phần cuối của Arrange-Act-Assert pattern.

1.Assert.AreEqual(enProcessState.Saved, _View.Model.ProcessState); Expect giá trị của ProcessState property là enProcessState.Saved.

Đây là tham chiếu toàn bộ file test

Listing 10: ContactPresenterTests.cs

01.[TestClass] 02.public class ContactPresenterTests 03.{ 04.ContactPresenter _Presenter = null; 05.IContactView _View = null; 06.[TestInitialize] 07.public void CreatePresenter() 08.{ 09.//Lets create first the view 10._View = MockRepository.GenerateStub<IContactView>(); 11.//Then lets create our Presenter 12._Presenter = new ContactPresenter(_View); 13._Presenter.HttpContext = MockContext(); 14.} 15.[TestMethod] 16.public void Should_Create_ContactRequest() 17.{ 18.ContactEventArgs args = new ContactEventArgs() 19.{ 20.Name = "John Katsiotis", 21.Email = "[email protected]", 22.Message = "Please contact me!" 23.}; 24._View.Raise(v => v.Contacting += null, _View, args); 25.Assert.AreEqual(enProcessState.Saved, _View.Model.ProcessState); 26.} 27.public HttpContextBase MockContext(bool isUserAuthenticated = false, string username = "") 28.{ 29.var httpContext = MockRepository.GenerateMock<HttpContextBase>(); 30.var identity = MockRepository.GenerateMock<IIdentity>(); 31.identity.Stub(u => u.Name).Return(username); 32.identity.Stub(i => i.IsAuthenticated).Return(isUserAuthenticated); 33.var user = MockRepository.GenerateMock<IPrincipal>(); 34.user.Stub(u => u.Identity).Return(identity); 35.httpContext.Stub(ctx => ctx.User).Return(user); 36.return httpContext; 37.} 38.}


All Rights Reserved