简体   繁体   中英

Testing business logic - MOQ - Visual Studio - MVC

I'm struggling to learn how to make tests, to test specifically and only business logic/rules in the Service layer only, using Moq tests.

Here's part of my project:

The Entity:

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

The Repository:

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();
    }        
}

The Repository Interface:

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

The Service:

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);
    }
}

My test:

[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);
    }

In this test I want to test the business logic which prevents me from saving a client with a name that has less than 2 characters. Of course, this test is working incorrectly, and I end up getting an exception.

I'd like to know how to implement a test to test these 2 business requirements, and only that, in my project:

  1. Can't save a client with less than 2 characters in the name.
  2. Can't delete a client whose name starts with "A".

I'm using Microsoft Visual Studio 2010, Entity Framework 4.0 and Moq.

I appreciate any kind of help I can get, and suggestions of changes that I should make in the project, only if it isn't possible to accomplish what I want with my actual project pattern.

First of all, Inside the ClientService , you are creating an instance of your data access layer, ClientRepository by hard coding. This will make it hard to test it. So the better approach is to Inject the IRepository Implementation to the ClientService.

public class ClientService
{        
    private IClientRepository repo;        

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

Now for your tests, Your Validate method is throwing an exception when the Name property value has less than 2 chars. To test this, you can decorate your test method with ExpectedException attribute.

[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);
}

My recommendation is to not throw the general Exception, but use some specific exceptions like ArgumentException etc or your custom exception if you want.

For your second test where you want to pass a Name property value which has more than 2 chars, It should not throw the exception from the Validate. In this test, We will mock the Save behavior so that it won't actually try to save to the db ( which it should not)

[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 :)
}

My last suggestion is to extract out the validation code to a new class/interface combo and inject your Validator implementation to the ClientService. With this approach, you can inject another version of validation as needed.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM