尽管安装,Moq仍然调用原始的内部方法

时间:2016-12-13 15:04:44

标签: c# moq

您好,

我无法让我的课程调用Moq方法。因此我的情况是:

public class ResetPasswordsTask : IRefreshTimeTask
{
    public long ExecutionId => 2100;
    public bool Enabled => true;
    public Dictionary<string, object> Params { get; set; }
    public KeyValuePair<string, Type>[] RequiredParams => new[]
    {
        new KeyValuePair<string, Type>("targetConfigs", typeof(InMemoryConfiguration))
    };
    public ILogger Logger { get; set; }

    internal IConfiguration SandboxConfig;
    internal IPartnerService PartnerService;
    internal ISalesForceBulkDataInserter DataInserter;

    public void Execute()
    {
        SandboxConfig = (IConfiguration)Params["targetConfigs"];
        PartnerService = Login.SalesforceApiLogin(true, SandboxConfig);
        DataInserter = new SalesForceBulkDataInserter();
        //InitialiseImapClient();

        // Retrieve users
        var users = TestAutomation.Salesforce.Pages.Base.User.GetUsers(PartnerService, SandboxConfig.Refresh_Usernames);

        // Upsert emails
        var emailUpsertResults = UpsertEmails(users, SandboxConfig.IMAP_Email);

        // Hit mailbox and visit confirmation links
        var activatedUsers = emailUpsertResults.Values.Where(r => r.Status == EmailResetStatusEnum.Success).Select(r => r.User).ToList();
        var confirmationLinkResults = ConfirmEmailChanges(activatedUsers);

        // Upsert passwords
        // All the users, except those for whom email update failed
        var passwordUpdateUsers = users.Except(confirmationLinkResults.Values.Where(r => !r.Success).Select(r => r.User)).ToList();
        var passwordUpsertResults = UpsertPasswords(passwordUpdateUsers);

        // Hit mailbox for new tokens
        var completeResetResults = RetrieveTokens(passwordUpsertResults.Values.Where(r => r.Success));

        var output = string.Join("\n", completeResetResults.Values.Where(c => c.Success).Select(result => string.Join("\t", new List<string> { result.User.Username, result.Password, result.SecurityToken })));
        //Logger.Info(output);
        Logger.Info(System.IO.Path.GetDirectoryName("."));
        Logger.Info(System.Reflection.Assembly.GetExecutingAssembly().Location);
        OpenSslEncryption.EncryptToFile(SandboxConfig.EncryptionPassword, "passwords.enc", output);
    }

    // Results are keyed by username
    internal IDictionary<string, EmailResetResult> UpsertEmails(ICollection<User> users, string newEmail)
    {
        var results = users.ToDictionary(u => u.Username, u => new EmailResetResult(u));

        Logger.Info($"Updating emails for {users.Count} users");
        foreach (var user in users)
        {
            Logger.Info($"updating email for {user.Username} users");
            var result = results[user.Username];

            // Prevent upserting the profile
            if (user.ProfileId != null)
            {
                Logger.Info("Preventing profile upsert");
                user.ProfileId.SalesForceId = user.ProfileId.OriginId;
            }

            // If the user has had their email set to what we're setting now, they can be skipped
            if (user.Email.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase) && user.IsActive)
            {
                Logger.Info($"User {user.Username} has their email set to {newEmail}; skipping");
                result.Status = EmailResetStatusEnum.Skipped;
                continue;
            }

            // Otherwise, set the email and carry on
            user.Email = newEmail;
            user.IsActive = true;

            // dataInserter.Upsert won't produce errors individually, and instead only log faulty upserts
            try
            {
                DataInserter.Upsert(new List<User> { user });
                Logger.Info($"Activated user {user.Username}");
                result.Status = EmailResetStatusEnum.Success;
            }
            catch (Exception e)
            {
                var error = $"Failed to update the email for user {user.Username} to {newEmail}. Error details: {e}";
                Logger.Error(TestAutomation.Framework.Core.Logger.Logger.FormatJson(error));
                result.ErrorMessage = e.ToString();
                result.Status = EmailResetStatusEnum.Failure;
            }
        }
        return results;
    }

    internal IDictionary<string, Result> ConfirmEmailChanges(ICollection<User> users)
    {
        var results = users.ToDictionary(u => u.Username, u => new Result(u));

        // Ran as a task to have a timeout
        Task.Run(() => ConfirmEmailChangesTask(results, users)).Wait(TimeSpan.FromMinutes(users.Count * 5));

        return results;
    }

    internal void ConfirmEmailChangesTask(IDictionary<string, Result> results, ICollection<User> users)
    {
        var remainingUsers = new HashSet<User>(users);
        while (true)
        {
            // Wait a bit; either for the emails to come in, or to give the webserver breathing room
            Thread.Sleep(new TimeSpan(0, 0, 15));

            Logger.Info($"Opening mailbox for {SandboxConfig.IMAP_Email}");
            using (var imapClient = CreateImapClient())
            {
                var messages = imapClient.SearchMessages(SearchQuery.NotSeen.And(SearchQuery.DeliveredAfter(DateTime.Now.AddHours(-1))));
                Logger.Info($"Found {messages.Count} messages");

                var remainingUsersCopy = new HashSet<User>(remainingUsers);
                Logger.Info($"Attempting to confirm emails for {remainingUsers.Count} users");
                foreach (var user in remainingUsersCopy)
                {
                    Logger.Info("Attempting to confirm email change for " + user.Username);
                    foreach (var message in messages.Where(m => m.MimeMessage.TextBody.Contains(user.Username) && m.MimeMessage.Subject.Contains("Sandbox: Finish changing your Salesforce")))
                    {
                        Logger.Info("Message found");
                        var confirmLink = GetEmailConfirmationLink(message);
                        if (confirmLink == null) continue;

                        // Visit the URL
                        var request = WebRequest.Create(confirmLink);
                        request.Timeout = (int)TimeSpan.FromSeconds(20).TotalMilliseconds;
                        var result = results[user.Username];
                        try
                        {
                            using (var response = (HttpWebResponse)request.GetResponse())
                            {
                                var statusCode = response.StatusCode;
                                if (statusCode != HttpStatusCode.OK)
                                {
                                    var error = $"Failed to load the email change confirmation link: {confirmLink}. HTTP Response: ({statusCode})";
                                    Logger.Error(TestAutomation.Framework.Core.Logger.Logger.FormatJson(error));
                                    result.Success = false;
                                    result.ErrorMessage = error;
                                }
                            }
                        }
                        catch (WebException e)
                        {
                            Logger.Error($"Request failed: {e.Message}\nWill retry later");
                            continue;
                        }

                        result.Success = true;
                        remainingUsers.Remove(user);
                        imapClient.MarkAsRead(message);

                        //Break down mailbox checks
                        Thread.Sleep(new TimeSpan(0, 0, 1));
                    }
                }
            }

            if (!remainingUsers.Any())
                break;
        }
    }

    #region MailboxInteraction

    internal static string GetEmailConfirmationLink(Message message)
    {
        // Extract confirmation URL
        var confirmLinkMatch = Regex.Match(message.MimeMessage.TextBody, @"([a-z]+:\/\/.*\.salesforce\.com\/\S*)");
        return !confirmLinkMatch.Success ? null : confirmLinkMatch.Groups[1].Value;
    }

    internal static string GetSecurityToken(Message message)
    {
        var tokenMatch = Regex.Match(message.MimeMessage.TextBody, @"Security token \(case-sensitive\): (?<token>\w+)");
        return !tokenMatch.Success ? null : tokenMatch.Groups[1].Value;
    }

    internal virtual IMailClient CreateImapClient()
    {
        return new IMAPClient(SandboxConfig.IMAP_Username, SandboxConfig.IMAP_Password, SandboxConfig.IMAP_URL);
    }

    #endregion
}

测试类:

[TestFixture]
public class WhenResettingUserPasswords
{
    private const string ConfirmationLink = "test://testdomain.salesforce.com/test/";

    [OneTimeSetUp]
    public void WebRequestSetup()
    {
        WebRequest.RegisterPrefix("test", TestableWebRequestCreateFactory.GetFactory());
        var uri = new Uri("test://testdomain.salesforce.com/test/");
        var expectedRequest = new TestableWebRequest(uri);
        expectedRequest.EnqueueResponse(HttpStatusCode.OK, "Success", "Even more success!", false);
        TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);
    }

    private static SetupBag Setup()
    {
        var bag = new SetupBag
        {
            Logger = new InMemoryLogger(),
            EmailConfirmationLink = ConfirmationLink,
            SecurityToken = "TheSecurityToken",
            Environment = "EnvName",
            EnvironmentUrl = "http://aaa.bbb.ccc/",
            User = new User
            {
                IsActive = false,
                Username = "joe.bloggs@company.com",
                Email = "joe.bloggs=company.com@example.com",
                OriginId = "ABCDEFGHIJKLMNO"
            }
        };


        var task = new Mock<Tasks.ResetPasswordsTask>(MockBehavior.Strict) { CallBase = true };
        task.Object.Logger = bag.Logger;

        var confirmMessage = new Message
        {
            UID = new UniqueId(0),
            MimeMessage = new MimeMessage
            {
                Subject = "Sandbox: Finish changing your Salesforce",
                Body = new TextPart("plain") { Text = "Confirm email change for joe.bloggs@company.com: " + bag.EmailConfirmationLink }
            }
        };
        var tokenMessage = new Message
        {
            UID = new UniqueId(1),
            MimeMessage = new MimeMessage
            {
                Subject = "Sandbox: Your new Salesforce security token",
                Body = new TextPart("plain") { Text = "New security token for joe.bloggs@company.com: " + bag.SecurityToken }
            }
        };

        var mailClientMock = new Mock<IMailClient>(MockBehavior.Strict);
        mailClientMock.Setup(m => m.SearchMessages(It.IsAny<SearchQuery>())).Returns(new List<Message> { confirmMessage, tokenMessage });
        task.Setup(t => t.CreateImapClient()).Returns(() => mailClientMock.Object);

        var dataInserterMock = new Mock<ISalesForceBulkDataInserter>();
        dataInserterMock.Setup(m => m.Upsert(It.IsAny<List<User>>(), false));

        var config = new InMemoryConfiguration
        {
            IMAP_Email = "test.email@company.com"
        };
        task.Object.SandboxConfig = config;

        bag.Task = task;

        return bag;
    }

    [Test]
    public void UpsertEmailsTest()
    {
        var bag = Setup();
        var task = bag.Task;

        var output = task.Object.ConfirmEmailChanges(new[] { bag.User });

        Assert.IsTrue(output.ContainsKey(bag.User.Username));
        Assert.IsTrue(output[bag.User.Username].Success);
        Assert.IsEmpty(output[bag.User.Username].ErrorMessage);
        Assert.AreEqual(task.Object.SandboxConfig.IMAP_Email, output[bag.User.Username].User.Username);
    }
}

现在,结果是调用task.Object.ConfirmEmailChanges()会引发一个关于new IMAPClient()为空的参数的异常,这个参数根本就不应该被调用。我找不到任何明显错误的东西,但那可能只是因为我对Moq或我的代码库进行测试的方式不够熟悉。

是的,我知道我在嘲笑我要测试的课程。我知道这是一个坏主意,我的目标是重新设计。我不能将IMailClient作为依赖项注入,因为它需要经常重新实例化。我也看到过在我们的代码库中使用的这种技术,它似乎有效。

2 个答案:

答案 0 :(得分:1)

您需要将方法声明为virtual,否则Moq无法覆盖它。 C#与Java不同 - 只有特别标记的成员才能被覆盖。

修改

您发布的代码应该可以使用 - 这是我根据您的代码生成的Linqpad MVCE,证明它可以正常工作。因此,您需要发布实际代码,因为它中存在错误。

void Main()
{
    // Arrange
    var foo = new Mock<Foo> { CallBase = true };
    var bar = new Mock<IBar>();
    bar.Setup(b => b.Value).Returns(2);
    // setup an IBar mock
    foo.Setup(f => f.CreateBar()).Returns(bar.Object);

    // Act
    var results = foo.Object.DoStuff();

    results.Dump(); // Prints "2"
}

public class Foo
{
    public int DoStuff()
    {
        var bar = CreateBar();
        return bar.Value;
    }

    public virtual IBar CreateBar()
    {
        return new RealBar();
    }
}

public interface IBar 
{
    int Value { get;}
}

public class RealBar : IBar
{
    public int Value
    {
        get { return 1; }
    }
}

答案 1 :(得分:1)

如果您正在嘲笑具体类而不是界面,那么您必须创建要模拟virtual的方法。

编辑:在您的新代码中,该方法也标记为internal。请参阅this question