2009-03-21 34 views
56

Điều này có lẽ sẽ trở thành trường hợp chỉ cần một cặp mắt khác. Tôi phải thiếu một cái gì đó, nhưng tôi không thể hiểu tại sao loại điều này không thể được kiểm tra. Tôi về cơ bản cố gắng để đảm bảo rằng người dùng không được thẩm định không thể truy cập vào xem bằng cách đánh dấu các bộ điều khiển với [Duyệt] thuộc tính và tôi đang cố gắng để kiểm tra này bằng cách sử dụng đoạn mã sau:Kiểm thử đơn vị ASP.Net MVC Ủy quyền thuộc tính để xác minh chuyển hướng đến trang đăng nhập

[Fact] 
public void ShouldRedirectToLoginForUnauthenticatedUsers() 
{ 
    var mockControllerContext = new Mock<ControllerContext>() 
         { DefaultValue = DefaultValue.Mock }; 
    var controller = new MyAdminController() 
       {ControllerContext = mockControllerContext.Object}; 
    mockControllerContext.Setup(c => 
       c.HttpContext.Request.IsAuthenticated).Returns(false); 
    var result = controller.Index(); 
    Assert.IsAssignableFrom<RedirectResult>(result); 
} 

Các RedirectResult Tôi đang tìm là một số dấu hiệu cho thấy người dùng đang được chuyển đến biểu mẫu đăng nhập, nhưng thay vào đó ViewResult luôn được trả về và khi gỡ lỗi tôi có thể thấy rằng phương thức Index() được nhấn thành công ngay cả khi người dùng không được xác thực.

Tôi có làm gì sai không? Thử nghiệm ở cấp độ sai? Tôi có nên thử nghiệm ở cấp độ tuyến đường cho loại điều này không?

Tôi biết rằng thuộc tính [Ủy quyền] đang hoạt động, vì khi tôi lật trang, màn hình đăng nhập thực sự bị ép buộc tôi - nhưng làm cách nào để xác minh điều này trong thử nghiệm?

Phương pháp chỉ mục và bộ điều khiển rất đơn giản chỉ để tôi có thể xác minh hành vi. Tôi đã bao gồm họ cho đầy đủ:

[Authorize] 
public class MyAdminController : Controller 
{ 
    public ActionResult Index() 
    { 
     return View(); 
    } 
} 

Bất kỳ giúp đánh giá cao ...

Trả lời

88

Bạn đang thử nghiệm ở cấp sai. Thuộc tính [Authorize] đảm bảo rằng công cụ routing sẽ không bao giờ gọi phương thức đó cho người dùng trái phép - RedirectResult sẽ thực sự xuất phát từ tuyến đường chứ không phải từ phương thức điều khiển của bạn.

Tin tốt là - đã có phạm vi kiểm tra cho điều này (như một phần của mã nguồn khung MVC), vì vậy tôi muốn nói rằng bạn không cần phải lo lắng về nó; chỉ cần chắc chắn rằng phương pháp điều khiển của bạn làm điều đúng khi nó được gọi, và tin tưởng khuôn khổ không gọi nó trong những hoàn cảnh sai.

EDIT: Nếu bạn muốn xác minh sự hiện diện của thuộc tính trong các bài kiểm tra đơn vị của bạn, bạn sẽ cần phải sử dụng sự phản chiếu để kiểm tra các phương pháp điều khiển của bạn như sau. Ví dụ này sẽ kiểm tra sự hiện diện của thuộc tính Authorize trên phương thức ChangePassword POST trong bản demo 'New ASP.NET MVC 2 Project' được cài đặt với MVC2.

[TestFixture] 
public class AccountControllerTests { 

    [Test] 
    public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() { 
     var controller = new AccountController(); 
     var type = controller.GetType(); 
     var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) }); 
     var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true); 
     Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method"); 
    } 
} 
+0

Cảm ơn Dylan - Tôi nghĩ rằng tôi có thể thử nghiệm ở cấp độ sai. Tôi hài lòng với ý tưởng "giả định" rằng nếu bộ điều khiển bị tấn công, người dùng được xác thực. P.S. Bạn có chắc là nó đã được thử nghiệm trong khuôn khổ không? Tôi có thể thấy một vài bài kiểm tra cung cấp IPrincipal hợp lệ, nhưng không ai kiểm tra trường hợp không hợp lệ ;-) – RobertTheGrey

+2

Er, no ... đã không thực sự xác minh rằng trường hợp kiểm tra bản thân mình; Tôi tin tưởng các băng đảng MVC đã có nó đúng. Lỗi của tôi! –

+3

Tôi thích câu trả lời cho lý do tại sao nó không phải là cách tiếp cận đúng, nhưng tôi không thuyết phục về đối số "tính năng này được thử nghiệm trong khuôn khổ và hoạt động". Tôi tin rằng thuộc tính đang hoạt động đúng, đó là công việc của khung công tác, nhưng tôi vẫn muốn khẳng định phương thức nào của các bộ điều khiển của tôi sử dụng thuộc tính. – Mathias

3

Tại sao không chỉ sử dụng phản chiếu để tìm thuộc tính [Authorize] trên lớp bộ điều khiển và/hoặc phương pháp hành động mà bạn đang thử nghiệm? Giả sử khung làm việc chắc chắn rằng Thuộc tính được vinh danh, đây sẽ là điều dễ nhất để làm.

+1

Có hai điều khác biệt được kiểm tra tại đây. (1) Kiểm tra rằng một thuộc tính tùy chỉnh thực hiện những gì nó phải làm; và (2) Bộ điều khiển/hành động vẫn được trang trí với thuộc tính. Bạn đang trả lời (2) nhưng tôi nghĩ rằng các liên kết được đăng bởi Dario Quintana câu trả lời tốt nhất để (1). –

+0

Trong thế giới thực, chú thích với thuộc tính Authorize không phải là cách duy nhất được sử dụng để cho phép các yêu cầu/hành động điều khiển. –

22

Có thể bạn đang thử nghiệm ở cấp độ sai nhưng thử nghiệm của nó có ý nghĩa. Ý tôi là, nếu tôi gắn cờ một phương thức với thuộc tính ủy quyền (Roles = "Superhero"), tôi không thực sự cần thử nghiệm nếu tôi gắn cờ nó. Những gì tôi (nghĩ rằng tôi) muốn là để kiểm tra rằng một người dùng trái phép không có quyền truy cập và người dùng được ủy quyền thực hiện.

Đối với một người sử dụng trái phép một thử nghiệm như thế này:

// Arrange 
var user = SetupUser(isAuthenticated, roles); 
var controller = SetupController(user); 

// Act 
SomeHelper.Invoke(controller => controller.MyAction()); 

// Assert 
Assert.AreEqual(401, 
    controller.ControllerContext.HttpContext.Response.StatusCode, "Status Code"); 

Vâng, nó không phải là dễ dàng và nó đã cho tôi 10 giờ, nhưng ở đây nó được. Tôi hy vọng ai đó có thể hưởng lợi từ nó hoặc thuyết phục tôi đi vào nghề khác.:) (BTW - Tôi đang sử dụng tê giác giả)

[Test] 
public void AuthenticatedNotIsUserRole_Should_RedirectToLogin() 
{ 
    // Arrange 
    var mocks = new MockRepository(); 
    var controller = new FriendsController(); 
    var httpContext = FakeHttpContext(mocks, true); 
    controller.ControllerContext = new ControllerContext 
    { 
     Controller = controller, 
     RequestContext = new RequestContext(httpContext, new RouteData()) 
    }; 

    httpContext.User.Expect(u => u.IsInRole("User")).Return(false); 
    mocks.ReplayAll(); 

    // Act 
    var result = 
     controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Index"); 
    var statusCode = httpContext.Response.StatusCode; 

    // Assert 
    Assert.IsTrue(result, "Invoker Result"); 
    Assert.AreEqual(401, statusCode, "Status Code"); 
    mocks.VerifyAll(); 
} 

Mặc dù, đó không phải là rất hữu ích mà không hàm helper này:

public static HttpContextBase FakeHttpContext(MockRepository mocks, bool isAuthenticated) 
{ 
    var context = mocks.StrictMock<HttpContextBase>(); 
    var request = mocks.StrictMock<HttpRequestBase>(); 
    var response = mocks.StrictMock<HttpResponseBase>(); 
    var session = mocks.StrictMock<HttpSessionStateBase>(); 
    var server = mocks.StrictMock<HttpServerUtilityBase>(); 
    var cachePolicy = mocks.Stub<HttpCachePolicyBase>(); 
    var user = mocks.StrictMock<IPrincipal>(); 
    var identity = mocks.StrictMock<IIdentity>(); 
    var itemDictionary = new Dictionary<object, object>(); 

    identity.Expect(id => id.IsAuthenticated).Return(isAuthenticated); 
    user.Expect(u => u.Identity).Return(identity).Repeat.Any(); 

    context.Expect(c => c.User).PropertyBehavior(); 
    context.User = user; 
    context.Expect(ctx => ctx.Items).Return(itemDictionary).Repeat.Any(); 
    context.Expect(ctx => ctx.Request).Return(request).Repeat.Any(); 
    context.Expect(ctx => ctx.Response).Return(response).Repeat.Any(); 
    context.Expect(ctx => ctx.Session).Return(session).Repeat.Any(); 
    context.Expect(ctx => ctx.Server).Return(server).Repeat.Any(); 

    response.Expect(r => r.Cache).Return(cachePolicy).Repeat.Any(); 
    response.Expect(r => r.StatusCode).PropertyBehavior(); 

    return context; 
} 

Vì vậy mà được bạn xác nhận rằng người dùng không trong một vai trò don' t có quyền truy cập. Tôi đã thử viết một bài kiểm tra để xác nhận ngược lại, nhưng sau hai giờ đào qua hệ thống ống nước mvc, tôi sẽ để nó cho người kiểm tra thủ công. (Tôi bailed khi tôi đã đến lớp VirtualPathProviderViewEngine. WTF? Tôi không muốn bất cứ điều gì để làm một VirtualPath hoặc một nhà cung cấp hoặc ViewEngine nhiều công đoàn của ba!)

Tôi tò mò là tại sao điều này là khó khăn trong khuôn khổ "có thể kiểm chứng" bị cáo buộc.

+0

WTF thực sự, may mắn nếu u dính vào nó u có thể tìm thấy một cách xung quanh nó và xung quanh tất cả các vấn đề sau nó, giống như tôi đã làm. Hãy loook tại dự án github của tôi tại: https://github.com/ibrahimbensalah/Xania.AspNet.Simulator/blob/master/Xania.AspNet.Simulator.Tests/ –

+0

Bài đăng này hoàn toàn _almost_ giống như [liên kết ] (https://web.archive.org/web/20130213143434/http://darioquintana.com.ar/blogging/2009/05/23/aspnet-mvc-testing-a-custom-authorize-filters) được tham chiếu trong bài đăng của @Dario. Bạn đã tự mình phát triển? –

+0

Có, nó được phát triển một mình, và vẫn tích cực phát triển. hiện đang hỗ trợ mvc4 và mvc5 từ ủy quyền, trình kết nối mô hình, xác nhận yêu cầu, hiển thị dao cạo .... –

1

Tôi không đồng ý với câu trả lời của Dylan, bởi vì 'người dùng phải đăng nhập' không có nghĩa là 'phương pháp điều khiển được chú thích với AuthorizeAttribute'

để đảm bảo 'người dùng phải đăng nhập' khi bạn gọi phương pháp hành động, khuôn khổ ASP.NET MVC làm điều gì đó như thế này (chỉ cần giữ trên, nó sẽ được đơn giản hơn cuối cùng)

let $filters = All associated filter attributes which implement 
       IAuthorizationFilter 

let $invoker = instance of type ControllerActionInvoker 
let $ctrlCtx = instance or mock of type ControllerContext 
let $actionDesc = instance or mock of type ActionDescriptor 
let $authzCtx = $invoker.InvokeAuthorizationFilters($ctrlCtx, $filters, $actionDesc); 

then controller action is authorized when $authzCtx.Result is not null 

Thật khó có thể thực hiện kịch bản giả này trong C# code làm việc. Có khả năng, Xania.AspNet.Simulator làm cho nó thực sự đơn giản để thiết lập một thử nghiệm như thế này và thực hiện chính xác các bước dưới nắp. đây là một ví dụ.

đầu tiên cài đặt các gói từ NuGet (phiên bản 1.4.0-beta4 tại thời điểm viết bài)

PM> cài đặt gói Xania.AspNet.Simulator -Pre

Sau đó thử nghiệm của bạn phương pháp có thể trông giống như thế này (giả sử NUnit và FluentAssertions được cài đặt):

[Test] 
public void AnonymousUserIsNotAuthorized() 
{ 
    // arrange 
    var action = new ProfileController().Action(c => c.Index()); 
    // act 
    var result = action.GetAuthorizationResult(); 
    // assert 
    result.Should().NotBeNull(); 
} 

[Test] 
public void LoggedInUserIsAuthorized() 
{ 
    // arrange 
    var action = new ProfileController().Action(c => c.Index()) 
    // simulate authenticated user 
    .Authenticate("user1", new []{"role1"}); 
    // act 
    var result = action.GetAuthorizationResult(); 
    // assert 
    result.Should().BeNull(); 
} 
Các vấn đề liên quan