我正在尝试使用.net核心中的Identity模拟生成电子邮件令牌

时间:2016-10-04 13:39:55

标签: c# unit-testing mocking asp.net-core

我正在尝试在.Net核心中专门针对身份进行一些模拟。 我创建了一个.Net核心类库项目,我相信我拥有所有正确的引用。我试图模拟的第一件事是Identity方法GenerateConfirmationTokenAsync方法,因为我正在尝试模拟注册用户的设置。 这是我在测试课中到目前为止所使用的moq和Xunit。

public class ServiceTests
{
    private readonly string _email;
    private readonly string _subject;
    private readonly string _message;
    private readonly Mock<IAccountService> _accountService;
    private readonly ApplicationUser _applicationUser;
    private readonly Mock<VisualJobsDbContext> _identityDbContext;

    public ServiceTests()
    {
        _email = "me@gmail.com";
        _subject = "test from me";
        _message = "hello I got to you";
        _accountService = new Mock<IAccountService>();
        _applicationUser = new ApplicationUser { UserName = _email, Email = _email };
        _identityDbContext = new Mock<VisualJobsDbContext>();
    }

    [Fact]
    public async Task GenerateConfirmationToken()
    {
        Mock<DbSet<ApplicationUser>> userMock = DbSetMock.Create(_applicationUser);

        var register = await _accountService.Object.Register(userMock.Object.First(), "password");

        var token = await _accountService.Object.GenerateEmailConfirmationTokenAsync(userMock.Object.First());

        Assert.NotNull(token);
    }

我的ApplicationUser类继承IdentityUser'register','token'始终为null。如果我查看userMock,我也无法看到令牌。

1 个答案:

答案 0 :(得分:1)

为了测试模拟,你首先需要将其设置为执行某些操作,否则它只是一个没有实现的空接口。

在您的具体情况下,您似乎根本不需要模拟。您创建ApplicationUser用户的实例,从中创建一个DbSet模拟,然后从中取出第一个对象。这与将ApplicationUser实例直接传递给您的服务相同。

相反,您正在将ApplicationUser传递给模拟,它根本没有实现,因此您实际上根本不测试任何内容。

public class ServiceTests
{
    private readonly string _email;
    private readonly string _subject;
    private readonly string _message;
    private readonly Mock<IAccountService> _accountService;
    private readonly ApplicationUser _applicationUser;
    private readonly Mock<VisualJobsDbContext> _identityDbContext;

    public ServiceTests()
    {
        _email = "me@gmail.com";
        _subject = "test from me";
        _message = "hello I got to you";
        _applicationUser = new ApplicationUser { UserName = _email, Email = _email };
    }

    [Fact]
    public async Task GenerateConfirmationToken()
    {
        // Does it have dependencies? If yes, you may need to mock them
        var _accountService = new AccountService(.../*mocked dependencies*/);
        var register = await _accountService.Register(_applicationUser, "password");

        var token = await _accountService.GenerateEmailConfirmationTokenAsync(userMock.Object.First());

        Assert.NotNull(token);
    }

现在,这取决于您在RegisterGenerateEmailConfirmationTokenAsync中的逻辑类型以及您在AccountService中具有哪些依赖关系(如果您需要moq或不是moq)。

假设您有一个名为TokenGenerator的服务,它实现了ITokenGenerator接口。

public class ServiceTests
{
    private readonly string _email;
    private readonly string _subject;
    private readonly string _message;
    private readonly ApplicationUser _applicationUser;
    private readonly Mock<ITokenGenerator> _tokenGenerator;

    public ServiceTests()
    {
        _email = "me@gmail.com";
        _subject = "test from me";
        _message = "hello I got to you";
        _applicationUser = new ApplicationUser { UserName = _email, Email = _email };
        _tokenGenerator = Mock<ITokenGenerator>();
    }

    [Fact]
    public async Task GenerateConfirmationToken()
    {
        // #### Setup ####
        // Reads: If GenerateToken method is called with the **exact** same instance as the user passed to the service
        _tokenGenerator.Setup(t => t.GenerateToken(It.Is(user)))
            // then return "abc123456" as token
            .Returns("abcd123456")
            // Verify that the method is called with the exact conditions from above, otherwise fail
            // i.e. if GenerateToken is called with a different instance of user, test will fail
            .Verifiable("ContainsKey not called.");

        // #### ACT ####

        // Pass the token generator mock to our account service
        var _accountService = new AccountService(_tokenGenerator.Object);
        var register = await _accountService.Register(_applicationUser, "password");
        var token = await _accountService.GenerateEmailConfirmationTokenAsync(userMock.Object.First());

        // #### VERIFY ####
        // Verify that GenerateToken method has been called with correct parameters
        _tokenGenerator.Verify();
        // verify that the GenerateEmailConfirmationTokenAsync returned the expected token abc123456
        Assert.Equals(token, "abcd123456");
    }

验证调用UserManager<T>.CreateAsync的示例:

        // #### SETUP ####
        var _userManager = new Mock<UserManager<ApplicationUser>>()
            .Setup(um => um.CreateAsync(It.Is(user))
            .Verifiable("UserManager.CreateAsync wasn't called!");
        var _accountService = new AccountService(_userManager);

        // #### ACT ####
        var register = await _accountService.Register(_applicationUser, "password");

        // #### VERIFY ####
        // Verify that GenerateToken method has been called with correct parameters
        _userManager.Verify();
        // verify that the GenerateEmailConfirmationTokenAsync returned the expected token abc123456

这可确保您的AccountServce.CreateAsync方法调用Identity。如果您稍后添加逻辑,这将确保在将来进行调用或测试将失败,如果您添加一些阻止CreateAsync被调用的逻辑。此测试测试行为。

修改

如果您不知道模拟的工作方式,请注意:UserManager<T>.CreateAsync的原始代码将从不执行。模拟完全覆盖了这个方法(因此它只适用于带有virtual方法的接口和类!!)并跳过它的整个逻辑,只返回一个预定义的值(在安装过程中你在.Returns(...)方法中指定的值模拟的)。

样机可以返回假/预定值,因此您可以通过确定的方式来测试类或功能。在集成测试中,模拟的值低于单元测试中的值,在单元测试中,您只想测试特定的代码片(单元),而不需要外部依赖,如数据库,文件系统或网络