0

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.

devlin carnate
  • 8,309
  • 7
  • 48
  • 82
Tony
  • 61
  • 2
  • 12
  • It would be helpful if you added the actual text of the exception you're receiving. – devlin carnate Nov 10 '15 at 17:44
  • Actually, i don't know how properly make tests with moq, i just put this test so you can see the mistakes i'm doing. Could you please provide me a working test that matchs the requirements that i'm looking for or point me to a direction that i can figure out on my own ? – Tony Nov 10 '15 at 17:48
  • So your test code is trying to make sure that an exception is thrown when the length of the name property is less than zero ... the answers to this question should shed some light on your question http://stackoverflow.com/questions/933613/how-do-i-use-assert-to-verify-that-an-exception-has-been-thrown .Don't think too much on the Moq, as that is not truly related to what you are trying to do – Sam.C Nov 10 '15 at 17:51
  • @Sam.C thanks man, this link really helped me. – Tony Nov 10 '15 at 22:57

1 Answers1

1

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
    }
}
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Thank you, i will try to follow your suggestions. – Tony Nov 10 '15 at 18:55
  • thank you very much man," [ExpectedException(typeof(Exception))] ", it really solves the problem. One question what are the options of ExpectedException, i mean how can i specify this to expect the same "string" that was thrown ? – Tony Nov 10 '15 at 20:48