Moq: модульное тестирование метода, основанного на HttpContext - PullRequest
38 голосов
/ 31 июля 2009

Рассмотрим метод в сборке .NET:

public static string GetSecurityContextUserName()
{             
 //extract the username from request              
 string sUser = HttpContext.Current.User.Identity.Name;
 //everything after the domain     
 sUser = sUser.Substring(sUser.IndexOf("\\") + 1).ToLower();

 return sUser;      
}

Я бы хотел вызвать этот метод из модульного теста с использованием инфраструктуры Moq. Эта сборка является частью решения webforms. Модульный тест выглядит так, но мне не хватает кода Moq.

//arrange 
 string ADAccount = "BUGSBUNNY";
 string fullADName = "LOONEYTUNES\BUGSBUNNY"; 

 //act    
 //need to mock up the HttpContext here somehow -- using Moq.
 string foundUserName = MyIdentityBL.GetSecurityContextUserName();

 //assert
 Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity.");

Вопрос :

  • Как я могу использовать Moq для организации поддельного объекта HttpContext с некоторым значением, например «MyDomain \ MyUser»?
  • Как мне связать эту фальшивку с моим вызовом в моем статическом методе в MyIdentityBL.GetSecurityContextUserName()?
  • Есть ли у вас какие-либо предложения по улучшению этого кода / архитектуры?

Ответы [ 6 ]

43 голосов
/ 31 июля 2009

Веб-формы общеизвестно непроверяемы именно по этой причине - большая часть кода может полагаться на статические классы в конвейере asp.net.

Чтобы проверить это с помощью Moq, вам необходимо провести рефакторинг вашего метода GetSecurityContextUserName(), чтобы использовать внедрение зависимостей с HttpContextBase объектом.

HttpContextWrapper находится в System.Web.Abstractions, который поставляется с .Net 3.5. Это оболочка для класса HttpContext, расширяющая HttpContextBase, и вы можете создать HttpContextWrapper так:

var wrapper = new HttpContextWrapper(HttpContext.Current);

Еще лучше, вы можете смоделировать HttpContextBase и настроить свои ожидания на нем, используя Moq. Включая вошедшего в систему пользователя и т. Д.

var mockContext = new Mock<HttpContextBase>();

Имея это в виду, вы можете позвонить GetSecurityContextUserName(mockContext.Object), и ваше приложение будет гораздо менее связано со статическим WebForms HttpContext. Если вы собираетесь проводить множество тестов, основанных на макете контекста, я настоятельно рекомендую взглянуть на класс MvcMockHelpers Скотта Хансельмана , который имеет версию для использования с Moq. Это удобно обрабатывает много необходимых настроек. И несмотря на название, вам не нужно делать это с MVC - я успешно использую его с приложениями веб-форм, когда я могу реорганизовать их для использования HttpContextBase.

3 голосов
/ 31 января 2012

В общем случае для модульного тестирования ASP.NET вместо доступа к HttpContext.Current у вас должно быть свойство типа HttpContextBase, значение которого задается путем внедрения зависимости (например, в ответе, предоставленном Womp).

Однако для тестирования функций, связанных с безопасностью, я бы рекомендовал использовать Thread.CurrentThread.Principal (вместо HttpContext.Current.User). Использование Thread.CurrentThread также имеет преимущество, заключающееся в возможности повторного использования вне веб-контекста (и работает одинаково в веб-контексте, поскольку платформа ASP.NET всегда устанавливает оба значения одинаково).

Чтобы затем протестировать Thread.CurrentThread.Principal, я обычно использую класс области видимости, который устанавливает Thread.CurrentThread в тестовое значение, а затем сбрасывает при утилизации:

using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
    // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}

Это хорошо согласуется со стандартным компонентом безопасности .NET, где у компонента есть известный интерфейс (IPrincipal) и местоположение (Thread.CurrentThread.Principal), и будет работать с любым кодом, который правильно использует / проверяет поток. CurrentThread.Principal.

Базовый класс области видимости будет выглядеть примерно так (при необходимости измените его, например, добавив роли):

class UserResetScope : IDisposable {
    private IPrincipal originalUser;
    public UserResetScope(string newUserName) {
        originalUser = Thread.CurrentPrincipal;
        var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]);
        Thread.CurrentPrincipal = newUser;
    }
    public IPrincipal OriginalUser { get { return this.originalUser; } }
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            Thread.CurrentPrincipal = originalUser;
        }
    }
}

Другой альтернативой является то, что вместо использования стандартного местоположения компонента безопасности напишите ваше приложение, чтобы использовать введенные данные безопасности, например, добавить свойство ISecurityContext с помощью метода GetCurrentUser () или аналогичного, а затем использовать его последовательно во всем приложении - но если вы собираетесь делать это в контексте веб-приложения, то вы также можете использовать предварительно созданный внедренный контекст , HttpContextBase.

1 голос
/ 16 апреля 2014
[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}

Также вы можете moq, как показано ниже

var controllerContext = new Mock<ControllerContext>();
      controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
      controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
1 голос
/ 13 марта 2012

Посмотрите на это http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

Используя класс httpSimulator, вы сможете передать HttpContext обработчику

HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?")
.SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx?
ticket=" + myticket + "&fileName=" + path));

FileHandler fh = new FileHandler();
fh.ProcessRequest(HttpContext.Current);

HttpSimulator реализует то, что нам нужно, чтобы получить экземпляр HttpContext. Так что вам не нужно использовать Moq здесь.

0 голосов
/ 31 июля 2009

Это на самом деле не связано с использованием Moq для модульного тестирования того, что вам нужно.

Как правило, мы работаем с многоуровневой архитектурой, где код на уровне представления действительно предназначен только для организации отображения в пользовательском интерфейсе. Этот вид кода не распространяется на модульные тесты. Вся остальная логика находится на бизнес-уровне, который не должен зависеть от уровня представления (т. Е. Ссылки на пользовательский интерфейс, такие как HttpContext), поскольку пользовательский интерфейс также может быть приложением WinForms, а не обязательно веб-приложением. .

Таким образом, вы можете избежать возни с Mock-фреймворками, пытаясь смоделировать HttpRequests и т. Д. ... хотя часто это все еще может быть необходимо.

0 голосов
/ 31 июля 2009

Если вы используете модель безопасности CLR (как и мы), вам нужно будет использовать некоторые абстрактные функции для получения и установки текущего участника, если вы хотите разрешить тестирование, и использовать их при получении или установке участника , Это позволяет вам получать / устанавливать принципала там, где это уместно (обычно в сети HttpContext и в текущем потоке в других местах, таких как модульные тесты). Это будет выглядеть примерно так:

public static IPrincipal GetCurrentPrincipal()
{
    return HttpContext.Current != null ?
        HttpContext.Current.User :
        Thread.CurrentThread.Principal;
}

public static void SetCurrentPrincipal(IPrincipal principal)
{
     if (HttpContext.Current != null) HttpContext.Current.User = principal'
     Thread.CurrentThread.Principal = principal;
}

Если вы используете пользовательский принципал, его можно довольно легко интегрировать в его интерфейс, например, ниже Current вызовет GetCurrentPrincipal, а SetAsCurrent вызовет SetCurrentPrincipal.

public class MyCustomPrincipal : IPrincipal
{
    public MyCustomPrincipal Current { get; }
    public bool HasCurrent { get; }
    public void SetAsCurrent();
}
...