Reputation: 8009
I have a Customer business entity which can be created only via CustomerFactory. However, I have questions as to how to perform unit testing for class that uses Customer object, because the unit test requires setup of Customer and its CustomerFactory whenever a customer is used.
The reason to using CustomerFactory rather than constructor is that it requires validation via ICustomerValidator. However putting the validation logic within constructor requires injecting the ICustomerValidator into constructor, which might not be desired.
For example, in performing unit test for implementation of ICustomerService, setup steps have to be created, including creation and setup of Customer and CustomerFactory (plus customer validation via ICustomerValidator, e.g IdValidFirstName). Furthermore, these setups have to be repeated for whenever Customer instance is used in unit test.
Below is the ICustomerService that is being unit-tested, but setup of Customer below has to created:
I want to unit-test this implementation
public interface ICustomerService
{
bool AddCustomer(Customer customer);
}
public class Customer
{
internal Customer()
{ }
public int Id { get; internal set; }
public string Firstname { get; internal set; }
public string Surname { get; internal set; }
public DateTime DateOfBirth { get; internal set; }
public string EmailAddress { get; internal set; }
public bool HasCreditLimit { get; internal set; }
public Company Company { get; internal set; }
//... other properties
}
Please note that both implementation of ICustomerService and ICustomerFactory depends on ICustomerValidator
public interface ICustomerFactory
{
Customer CreateCustomerWith(int id, string firstName, string surname, DateTime dateOfBirth, string emailAddress, Company company);
}
public class CustomerFactory : ICustomerFactory
{
private ICustomerValidator customerValidator;
public CustomerFactory(ICustomerValidator customerValidator)
{
this.customerValidator = customerValidator;
}
public Customer CreateCustomerWith(int id, string firstName, string surname, DateTime dateOfBirth,
string emailAddress, Company company)
{
//validation logic here ...
}
}
public interface ICustomerValidator
{
IDictionary<string, string> Errors { get; }
bool IsValid(Customer customer);
bool IsValidId(int id);
bool IsValidFirstName(string firstName);
bool IsValidSurname(string surname);
bool IsValidDateOfBirth(DateTime dateOfBirth);
bool IsValidEmailAddress(string emailAddress);
bool IsValidCompany(Company company);
}
But I have to setup this in performing unit-test for ICustomerService, which seems lots of setup, or Is it correct, or is there a better way (I can make it re-use, but still I have to call it in the test for ICustomerService, Is it the ONLY way)?
private Customer CreatCustomer()
{
var customerId = 0;
var firstName = "John";
var surname = "Tom";
var emailAddress = "[email protected]";
var dateOfBirth = new DateTime(2013, 1, 1);
var company = new Company(34, "Unit", Classification.VIP);
SetupForCustomerCreation(customerId,firstName,surname,emailAddress,dateOfBirth,company);
var customer = this.customerFactory
.CreateCustomerWith(
customerId, firstName, surname, dateOfBirth, emailAddress, company);
return customer;
}
private void SetupForCustomerCreation(int customerId, string firstName, string surname, string emailAddress, DateTime dateOfBirth, Company company)
{
this.customerValidatorMock.Setup(c =>
c.IsValidId(customerId)).Returns(true);
this.customerValidatorMock.Setup(c =>
c.IsValidFirstName(firstName)).Returns(true);
this.customerValidatorMock.Setup(c =>
c.IsValidSurname(surname)).Returns(true);
this.customerValidatorMock.Setup(c =>
c.IsValidDateOfBirth(dateOfBirth)).Returns(true);
this.customerValidatorMock.Setup(c =>
c.IsValidEmailAddress(emailAddress)).Returns(true);
this.customerValidatorMock.Setup(c =>
c.IsValidCompany(company)).Returns(true);
}
Thanks in advance!
Upvotes: 2
Views: 133
Reputation: 22310
While I would strongly recommend @Alexandr-Mihalciuc answer the the right approach, you can expose your assembly where the customer class is defined as InternalsVisible to your test assembly, and be able to create instances of the customer class in the test, even if you preserve your internal ctor.
Upvotes: 0
Reputation: 2577
I would recomment to make Customer constructor public. After doing so you can use it freely without touching its factory all the time. By introducing not public constrcutor to a class makes it implicitly dependent on its factory, which is in most of the cases not recommended. It is better to have loosely coupled classes as it makes it easier to mantain and test
Upvotes: 1