我正在努力学习如何进行测试,使用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个字符的客户端。当然,这个测试工作不正确,我最终得到了一个例外。
我想知道如何实施测试来测试这两个业务需求,并且只在我的项目中测试:
我正在使用Microsoft Visual Studio 2010,Entity Framework 4.0和Moq。
我感谢我能得到的任何帮助,以及我在项目中应该做出的改变建议,只有在我不能用我的实际项目模式完成我想要的时候。
答案 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
}
}