测试业务逻辑 - MOQ - Visual Studio - MVC

时间:2015-11-10 17:29:38

标签: c# asp.net-mvc visual-studio moq

我正在努力学习如何进行测试,使用Moq测试来专门测试服务层中的业务逻辑/规则。

这是我项目的一部分:

实体:

public class Client
{        
    public int Id { get; set; }
    public string Name { get; set; }        
}

存储库:

public class ClientRepository : IClientRepository
{

    private MyContext context;

    public ClientRepository(MyContext context)
    {
        this.context = context;
    }

    public void Save(Client client)
    {
        try
        {
            context.Clients.Add(client);
            context.SaveChanges();                
        }
        catch (Exception)
        {
            throw new Exception("Error saving");
        }
    }

    public void Delete(Client client)
    {
        Client cl = context.Clients.Where(x => x.Id == client.Id).FirstOrDefault();
        if (cl != null)
        {
            context.Clients.Remove(client);
            context.SaveChanges();
        }
        else
        {
            throw new Exception("Error deleting");
        }
    }

    public List<Client> List()
    {
        return context.Clients.ToList();
    }        

    public Client GetById(int Id)
    {
        return context.Clients.Where(x => x.Id == Id).FirstOrDefault();
    }        
}

存储库接口:

public interface IClientRepository
{
    void Save(Client client);
    void Delete(Client client);      
    List<Client> List();
    Client GetById(int Id);
}

服务:

public class ClientService
{        
    private ClientRepository rep;        

    public ClientService(MyContext ctx)
    {
        this.rep = new ClientRepository(ctx);
    }

    public void Save(Client client)
    {
        try
        {
            Validate(client);
            rep.Save(client);
        }
        catch (Exception ex) {
            Debug.WriteLine(ex.Message);
            throw new Exception("Error Saving"); 
        }                
    }

    public void Delete(Client client)
    {
        try
        {
            if (client.Name.StartsWith("A"))
            {
                throw new Exception("Can't delete client with name 
                                     starting with A");
            }
            rep.Delete(client);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            throw new Exception("Error deleting"); 
        }
    }

    public List<Client> List()
    {
        try
        {
            return rep.List();
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            throw new Exception("Error list");
        }
    }

    public void Validate(Client client)
    {
        if (client.Name.Length < 2)
        {
            throw new Exception("nome deve ser maior que 2");
        }            
    }

    public Client GetById(int Id)
    {
        return rep.GetById(Id);
    }
}

我的测试:

[TestMethod]
    public void CantSaveClientWithNameLengthLessThanTwo()
    {
        Client client = new Client() { Id = 4, Name = "a" };

        var mockMyContext = new Mock<MyContext>();

        mockMyContext.Setup(c => c.Clients.Add(client)).Throws<InvalidOperationException>();

        var service = new ClientService(mockMyContext.Object);

        service.Save(client);

        int listCount = service.List().Count();

        Assert.AreEqual(0, listCount);
    }

在此测试中,我想测试业务逻辑,该逻辑阻止我保存名称少于2个字符的客户端。当然,这个测试工作不正确,我最终得到了一个例外。

我想知道如何实施测试来测试这两个业务需求,并且只在我的项目中测试:

  1. 无法保存名称中少于2个字符的客户端。
  2. 无法删除名称以“A”开头的客户。
  3. 我正在使用Microsoft Visual Studio 2010,Entity Framework 4.0和Moq。

    我感谢我能得到的任何帮助,以及我在项目中应该做出的改变建议,只有在我不能用我的实际项目模式完成我想要的时候。

1 个答案:

答案 0 :(得分:1)

首先,在ClientService内,您正在通过硬编码创建数据访问层ClientRepository的实例。这将使其难以测试。因此,更好的方法是将IRepository实现注入ClientService。

public class ClientService
{        
    private IClientRepository repo;        

    public ClientService(IClientRepository repo)
    {
      this.repo = repo;
      // now use this.repo in your methods
    }
}

现在,对于您的测试,当Name属性值少于2个字符时,您的Validate方法会抛出异常。要对此进行测试,您可以使用ExpectedException属性修饰测试方法。

[TestMethod]
[ExpectedException(typeof(Exception))]
public void ValidateShouldThrowException()
{
  var moqRepo = new Mock<IRepository>();
  var cs = new ClientService(moqRepo.Object); 
  var client = new Client { Name = "S" };

  cs.Save(client);
}

我的建议是不抛出常规异常,但如果需要,可以使用ArgumentException等特定异常或自定义异常。

对于要传递具有2个以上字符的Name属性值的第二个测试,它不应该从Validate中抛出异常。在这个测试中,我们将模拟Save行为,以便它实际上不会尝试保存到db(它不应该)

[TestMethod]
public void SaveShouldWork()
{
  var moqRepo = new Mock<IRepository>();
  moqRepo.Setup(s=>s.Save(It.IsAny<Client>)).Verifiable();
  var cs = new ClientService(moqRepo.Object); 
  var client = new Client { Name = "S" };

  cs.Save(client);
  //Test passed :)
}

我的最后一个建议是将验证代码提取到新的类/接口组合,并将Validator实现注入ClientService。使用这种方法,您可以根据需要注入另一个版本的验证。

public class ClientService
{        
    private IClientRepository repo;        
    private IValidator validator;
    public ClientService(IClientRepository repo,IValidator validator)
    {
      this.repo = repo;
      this.validator = validator
    }
}