CustomMembershipProvider не работает из модульного тестирования
Написание модульных тестов, которые требуют доступа к базе данных через мой CustomMembershipProvider.
edit
-
public class CustomMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
using (var usersContext = new UsersContext())
{
var requiredUser = usersContext.GetUser(username, password);
var userApproved = usersContext.GetUserMem(username);
if (userApproved == null) return false;
return (requiredUser != null && userApproved.IsApproved != false);
}
}
}
[TestFixture]
public class AccountControllerTest
{
[Test]
public void ShouldNotAcceptInvalidUser()
{
// OPTION1
Mock<IMembershipService> membership = new Mock<IMembershipService>();
//OPTION2
// Mock<AccountMembershipService> membership = new Mock<AccountMembershipService>();
membership.Setup(m => m.ValidateUser(It.IsAny<string>(), It.IsAny<string>()))
.Returns(false);
var logonModel = new LoginModel() { EmailorUserName = "connorgerv", Password = "pasdsword1" };
var controller = new AccountController(membership.Object);
// Act
var result = controller.Login(logonModel,"Index") as RedirectResult;
// Assert
Assert.That(result.Url, Is.EqualTo("Index"));
Assert.False(controller.ModelState.IsValid);
Assert.That(controller.ModelState[""],
Is.EqualTo("The user name or password provided is incorrect."));
}
[Test]
public void ExampleForMockingAccountMembershipService()
{
var validUserName = "connorgerv";
var validPassword = "passwordd1";
var stubService = new Mock<CustomMembershipProvider>();
bool val = false;
stubService.Setup(x => x.ValidateUser(validUserName, validPassword)).Returns(true);
Assert.IsTrue(stubService.Object.ValidateUser(validUserName, validPassword));
}
}
public class AccountController : Controller
{
public IMembershipService MembershipService { get; set; }
public AccountController(IMembershipService service){
MembershipService=service;
}
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult Index()
{
return RedirectToAction("Profile");
}
public ActionResult Login()
{
if (User.Identity.IsAuthenticated)
{
//redirect to some other page
return RedirectToAction("Index", "Home");
}
return View();
}
//
// POST: /Account/Login
[HttpPost]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.EmailorUserName, model.Password))
{
SetupFormsAuthTicket(model.EmailorUserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
public class AccountMembershipService : IMembershipService
{
private readonly MembershipProvider _provider;
public AccountMembershipService()
: this(null)
{
}
public AccountMembershipService(MembershipProvider provider)
{
_provider = provider ?? Membership.Provider;
}
public virtual bool ValidateUser(string userName, string password)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
return _provider.ValidateUser(userName, password);
}
}
Membership in web.config of main application
<membership defaultProvider="CustomMembershipProvider">
<providers>
<clear />
<add name="CustomMembershipProvider" type="QUBBasketballMVC.Infrastructure.CustomMembershipProvider" connectionStringName="UsersContext" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
public class CustomMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
using (var usersContext = new UsersContext())
{
var requiredUser = usersContext.GetUser(username, password);
var userApproved = usersContext.GetUserMem(username);
if (userApproved == null) return false;
return (requiredUser != null && userApproved.IsApproved != false);
}
}
}
Что происходит, когда я бегу ShouldNotAcceptInvalidUser()
с необязательным Option1 я вижу, что MembershipService является Mock<IMembershipService>
в AccountController, но он никогда не входит в MembershipService.ValidateUser при действии входа в систему.
Когда я запускаю с необкомментированным option2, происходит то же самое, за исключением того, что MembershipService Mock<AccountMembershipService>
в accountcontroller, и он попадает в AccountMembership Contstructor с нулевыми параметрами, который в свою очередь устанавливает SqlMembershipProvider
как Membership.Provider
является System.Web.Security.SqlMembershipProvider
Также ExampleForMockingAccountMembershipService()
кажется, не ударил ValidateUser
метод вообще в CustomMembershipProvider
и всегда возвращает истину.
Надеюсь, этого достаточно, чтобы увидеть, где я иду не так!!:/
1 ответ
Спасибо за предоставление вашего кода. Я думаю, что у меня гораздо лучше получается справиться с тем, что ты сейчас пытаешься сделать.
Для тебя ShouldNotAcceptInvalidUser
тест, вы должны определенно издеваться IMembershipService
вместо AccountMembershipService
(выберите вариант 1 вместо варианта 2). Поскольку ваш контроллер - это ваша SUT, он должен быть единственным "реальным" классом в тесте, чтобы минимизировать количество движущихся частей.
С вариантом 1 нет никаких оснований ожидать, что MembershipService.ValidateUser
вступит в любой код. MembershipService
это фиктивный объект - и вы явно сказали ему просто всегда возвращать false
когда этот метод вызывается. Исходя из приведенного здесь кода и использования варианта 1, я ожидаю, что этот тест пройдёт.
В вашем другом тесте, ExampleForMockingAccountMembershipService
ты издеваешься над своим SUT, что ты не должен делать. Ваша SUT должна быть единственным "реальным" объектом в вашем тесте. Это означает, что все взаимодействующие объекты должны быть смоделированы, оставляя SUT единственным объектом, делающим что-либо значимое. (Таким образом, если тест не пройден, вы точно знаете, что это из-за ошибки в SUT.)
(Заметка: ValidateUser
всегда возвращался true
здесь, потому что вы издевались над SUT и явно сказали, чтобы он всегда возвращался true
, Вот почему никогда не стоит издеваться над SUT - насмешка меняет поведение, которое вы пытаетесь проверить.)
На основании предоставленного вами кода, я предполагаю, что причина, по которой вы высмеивали CustomMembershipProvider
потому что он не полностью реализует свой абстрактный базовый класс MembershipService
, Если это действительно так, то вам нужно будет реализовать отсутствующие методы вручную, вместо того, чтобы полагаться на среду разработки для обеспечения реализации по умолчанию.
Вот что, я полагаю, вы хотели, чтобы этот тест выглядел следующим образом:
[Test]
public void ExampleForMockingAccountMembershipService()
{
var validUserName = "connorgerv";
var validPassword = "passwordd1";
var sut = new CustomMembershipProvider();
Assert.IsTrue(sut.ValidateUser(validUserName, validPassword));
}
Здесь нужно обратить внимание на тот факт, что CustomMembershipProvider
создает одну из его зависимостей: UsersContext
, В модульном тесте, так как CustomMembershipProvider
ваша SUT, вы хотели бы издеваться над всеми его зависимостями. В этой ситуации вы можете использовать внедрение зависимостей для передачи объекта, ответственного за создание этой зависимости (например, IUsersContextFactory
), и используйте фиктивную фабрику и контекст в своем тесте.
Если вы не хотите идти по этому пути, просто знайте, что ваш тест может не пройти из-за ошибки в CustomMembershipProvider
или ошибка в UsersContext
,
Итак, общая логика в ваших тестах звучит правильно; проблемы в основном возникают из-за путаницы в роли фиктивных объектов в ваших тестах. Сначала это сложная концепция, но вот некоторые ресурсы, которые помогли мне, когда я изучал это: