Как смоделировать авторизацию OwinManager для тестирования моего контроллера API
Я использую авторизацию на основе ресурсов ThinkTecture в своем WebApi.
Я пытаюсь проверить один из моих контроллеров, который мне нужен, чтобы проверить доступ внутри функции. Но теперь я больше не могу тестировать функцию, так как я не могу смоделировать метод расширения, и, поскольку это метод nuget, я не могу изменить класс для добавления другого значения.
Мой контроллер выглядит так:
public class AlbumController : ApiController
{
public async Task<IHttpActionResult> Get(int id)
{
if (!(await Request.CheckAccessAsync(ChinookResources.AlbumActions.View,
ChinookResources.Album,
id.ToString())))
{
return this.AccessDenied();
}
return Ok();
}
}
И ResourceAuthorizationManager устанавливается в автозагрузку следующим образом:
app.UseResourceAuthorization(new ChinookAuthorization());
Исходный код проекта ThinkTecture находится здесь.
Спасибо за помощь
3 ответа
ResourceAuthorizationAttribute использует Reqest.CheckAccess, поэтому я не думаю, что это хорошее решение, чтобы абстрагировать реализацию и затем внедрить ее в контроллер, поскольку теоретически ResourceAuthorizationAttribute и созданная служба могут использовать разные реализации метода CheckAccess.
Я выбрал более простой подход, создав BaseController
public class BaseController : ApiController
{
public virtual Task<bool> CheckAccessAsync(string action, params string[] resources)
{
return Request.CheckAccessAsync(action, resources);
}
}
и сделать CheckAccessAsync виртуальным, чтобы я мог издеваться над ним (например, Moq).
тогда из моего контроллера
public class AlbumController : BaseController
{
public async Task<IHttpActionResult> Get(int id)
{
if (!(await CheckAccessAsync(ChinookResources.AlbumActions.View,
ChinookResources.Album,
id.ToString())))
{
return this.AccessDenied();
}
return Ok();
}
}
Модульное тестирование контроллера тогда так же просто, как:
[TestClass]
public class TestClass
{
Mock<AlbumController> mockedTarget
AlbumController target
[TestInitialize]
public void Init()
{
mockedTarget = new Mock<AlbumController>();
target = mockedTarget.Object;
}
[Test]
public void Test()
{
mockedTarget.Setup(x => x.CheckAccessAsync(It.IsAny<string>(),
It.IsAny<string[]>()))
.Returns(Task.FromResult(true));
var result = target.Get(1);
// Assert
}
}
Вы всегда можете обернуть этот статический вызов в какую-то свою абстракцию:
public interface IAuthorizationService
{
Task<bool> CheckAccessAsync(string view, string album, string id);
}
а затем есть некоторая реализация, которая делегирует вызов статическому методу расширения. Но теперь, так как вы будете работать с IAuthorizationService
Вы можете свободно издеваться над CheckAccessAsync
метод в ваших юнит-тестах.
Что касается тестирования реализации этой абстракции, вам, вероятно, она не нужна, поскольку она действует только как мост к классам ThinkTecture, которые уже должны быть достаточно хорошо протестированы.
Я наконец решил свою проблему. Настоящая проблема заключалась в том, что метод CheckAccess был расширением. (для моего ответа, каждый класс будет ссылаться на образец, который можно найти здесь)
Чтобы прекратить использовать метод расширения, я добавил эти методы в мой chinookAuthorization
public Task<bool> CheckAccessAsync(ClaimsPrincipal user, string action, params string[] resources)
{
var ctx = new ResourceAuthorizationContext(user ?? Principal.Anonymous, action, resources);
return CheckAccessAsync(ctx);
}
public Task<bool> CheckAccessAsync(ClaimsPrincipal user, IEnumerable<Claim> actions, IEnumerable<Claim> resources)
{
var authorizationContext = new ResourceAuthorizationContext(
user ?? Principal.Anonymous,
actions,
resources);
return CheckAccessAsync(authorizationContext);
}
Затем я изменил свой контроллер, чтобы иметь экземпляр ChinookAuthorization
public class AlbumController : ApiController
{
protected readonly chinookAuthorization chinookAuth;
public BaseApiController(chinookAuthorization chinookAuth)
{
if (chinookAuth == null)
throw new ArgumentNullException("chinookAuth");
this.chinookAuth = chinookAuth;
}
public async Task<IHttpActionResult> Get(int id)
{
if (!(await chinookAuth.CheckAccessAsync((ClaimsPrincipal)RequestContext.Principal, ChinookResources.AlbumActions.View,
ChinookResources.Album,
id.ToString())))
{
return this.AccessDenied();
}
return Ok();
}
}
И я до сих пор объявляю мою ChinookAuthorization при запуске моего owin, чтобы продолжать использовать тот же шаблон для моего вызова проверки доступа к атрибуту.
Так что теперь, я просто должен высмеивать chinookAuthorization, высмеивать ответ на вызов, чтобы вернуть true, и это все!