i'm trying to UnitTest my controller where an exception is catch by the ExceptionFilterAttribute and launched back as a HttpResponseException.
Controller
[ExceptionFilters] //ExceptionFilterAttribute
public class EleveController : ApiController
{
private IGpiRepository _gpiRepository;
public EleveController(IGpiRepository gpiRepository)
{
_gpiRepository = gpiRepository;
}
[HttpGet]
[Route("{fiche:int}/grouperepere")]
public GroupeRepere GroupeRepere(int fiche) //This What Im trying to test
{
GpiService service = new GpiService(_gpiRepository);
return service.Get(fiche); //Throw an ArgumentNullException when fiche == 0
}
}
ExceptionFilter
public class ExceptionFilters : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
else if (context.Exception is ArgumentNullException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(string.Format("Argument \"{0}\" is null or invalid", ((ArgumentNullException)context.Exception).ParamName)),
ReasonPhrase = "Argument null or invalid"
};
}
}
and this is my test:
private IGpiRepository _gpiRepository;
private Mock<ICallApi> _callApi;
private EleveController _controller;
[TestInitialize]
public void Initialize()
{
_callApi = new Mock<ICallApi>();
_gpiRepository = new GpiRepository(_callApi.Object);
_controller = new EleveController(_gpiRepository);
}
[TestMethod]
public void EleveController__GroupeRepere_WithBadFiche_400BadRequest()
{
string noGroupe = "111";
int fiche = 0;
try
{
GroupeRepere gp = _controller.GroupeRepere(fiche);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is HttpResponseException); // not working --> ArgumentNullException
}
}
The problem is that e still is an ArgumentNullException. When i go on debug, it doesn't even reach the ExceptionFilter class
Am i missing something?
Thanks.
Your test is directly against the controller. ExceptionFilterAttribute depends on a server.(remember: attributes are Metadata)
The way to test the behavior is to use IIS server or SelfHost Server, then raise the server in your test class and send the request:
[TestInitialize]
public void Initialize()
{
_callApi = new Mock<ICallApi>();
_gpiRepository = new GpiRepository(_callApi.Object);
//initialize your server
//set _gpiRepository as a dependency and etc..
}
[TestMethod]
public void EleveController__GroupeRepere_WithBadFiche_400BadRequest()
{
//configure the request
var result = client.ExecuteAsGet<GroupeRepere>(<your request>);
Assert.AreEqual(HttpStatusCode.BadRequest,result.StatusCode);
}
In my opinion you shouldn't error code unless your controller is apart of public Api.(the reason is simple this kind of tests are very simple to break, thay are slow and thay use expensive resources) if your controller is a part
public Api you should test it through your Acceptance tests, then you verify that nothing override the expected behavior.
If you still want to test this behavior
then i'd like to offer you an alternative way to test it:
Create UT against ExceptionFilters.
Create a UT which verifies that the method has ExceptionFilters attribute
For example:
[TestMethod]
public void GroupeRepere_HasExceptionFiltersAttribute()
{
var attribute = typeof (UnitTest2).GetMethod("GroupeRepere").GetCustomAttributes(true);
foreach (var att in attribute)
{
if(att.GetType() is typeof(ExceptionFilters))
{
return;
}
}
Assert.Fail();
}
Pros:
it' fast, not so easy to break, it doesn't use expensive reasorces.
Cons:
In production some setting could override the expected behavior.
Related
I am trying to test the Exception handler in my application.
However I can't generate an Exception for the Constructor.
Normally, I would create a moq of an Object and then do a setup where a call to Object.method throws an Exception. Then simply detect the Exception in the Test.
However, in this Constructor the only call is:
CredentialProfileStoreChain.TryGetAWSCredentials
CredentialProfileStoreChain.TryGetAWSCredentials can't be overridden so I can't use moq Setup to generate an Exception.
Code:
public class AWSDynamoDbManager : IAWSDynamoDbManager
{
private readonly ILogger _logger;
private readonly AmazonDynamoDBClient _dynamoDbClient;
//NOTE: This setting is in the app.config of the calling application so that different uses can use different profiles
private readonly string _awsProfileName = ConfigurationManager.AppSettings["AWSProfileName"];
public AWSDynamoDbManager(CredentialProfileStoreChain chain, ILogger logger)
{
this._logger = logger;
try
{
AWSCredentials awsCredentials;
chain.TryGetAWSCredentials(_awsProfileName, out awsCredentials);
_dynamoDbClient = new AmazonDynamoDBClient(awsCredentials, RegionEndpoint.EUWest2);
}
catch (Exception e)
{
logger.Error("Could Not Open DynamoDB");
logger.Error("Error: " + e.Message);
throw;
}
}
}
Test:
public void TestToSeeIfWeCatchTheExceptionIfWeCannotConnectToTheDatabase()
{
// arrange
var mockLogger = new Mock<ILogger>();
var mockChain = new Mock<CredentialProfileStoreChain>();
// act / assert
Assert.Catch<ArgumentException>(() => new AWSDynamoDbManager(mockChain.Object, mockLogger.Object));
}
What can I use to force the Constructor to cause an Exception?
when my hands are tied because of an external dependency or static type, i use a wrapper, so that's what i'd use here. since we can't mock a CredentialProfileStoreChain, we throw it in a wrapper and use the wrapper.
public interface ICredentialProfileStoreChainWrapper
{
void TryGetAWSCredentials(/*TODO*/);
}
public class CredentialProfileStoreChainWrapper
{
readonly CredentialProfileStoreChain _Chain;
public CredentialProfileStoreChainWrapper(CredentialProfileStoreChain chain)
{
_Chain = chain;
}
public void TryGetAWSCredentials(/*TODO*/)
{
_Chain.TryGetAWSCredentials(/*TODO*/);
}
}
public class AWSDynamoDbManager : IAWSDynamoDbManager
{
public AWSDynamoDbManager(ICredentialProfileStoreChainWrapper chainWrapper, ILogger logger)
{
//TODO
chainWrapper.TryGetAWSCredentials(/*TODO*/);
}
}
public class Tests
{
[Test]
public void TestToSeeIfWeCatchTheExceptionIfWeCannotConnectToTheDatabase()
{
var wrapper = new Mock<ICredentialProfileStoreChainWrapper>();
var logger = new Mock<ILogger>();
var manager = new AWSDynamoDbManager(wrapper.Object, logger.Object);
wrapper.Setup(s => s.TryGetAWSCredentials(/*TODO*/)).Throws(new Exception());
//TODO
}
}
So I'm trying to write a simple tablecontroller Unit test for my backend??
I havent been able to do so, all I've achieve is writing unit testing for ApiControllers but is there a way to write a Unit test for TableControllers?
What I'll like to do is this:
public class AuctionController : TableController<Auction>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Auction>(context, Request);
}
// GET tables/Auction
public IQueryable<Auction> GetAllAuction()
{
return Query();
}
// GET tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Auction> GetAuction(string id)
{
return Lookup(id);
}
// PATCH tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Auction> PatchAuction(string id, Delta<Auction> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Auction
public async Task<IHttpActionResult> PostAuction(Auction item)
{
Auction current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteAuction(string id)
{
return DeleteAsync(id);
}
}
and i wish to make a test controller like this one:
[TestClass]
public class AuctionControllerTests
{
private readonly AuctionController _controller;
public AuctionControllerTests()
{
_controller = new AuctionController();
}
[TestMethod]
public void Fetch_all_existing_items()
{
Assert.Equal(2, _controller.GetAllTodoItems().ToList().Count);
}
}
how can I possibly be able to get this to work??? Please I would appreciate your help a lot.
Yes. it is possible but you code is not unit testable. Here are the steps for you
Find a way inject your depedencies MobileServiceContext and DomainManager
You need to set up contexts and requests etc as in shown in the following code.
(Code assumes you are using Moq)
public class ControllerUnitTestBase<T> where T: Controller
{
private Action<RouteCollection> _routeRegistrar;
private Mock<HttpRequestBase> _mockRequest;
protected virtual Action<RouteCollection> RouteRegistrar
{
get { return _routeRegistrar ?? DefaultRouteRegistrar; }
set { _routeRegistrar = value; }
}
protected Mock<HttpRequestBase> MockRequest
{
get
{
if (_mockRequest == null)
{
_mockRequest = new Mock<HttpRequestBase>();
}
return _mockRequest;
}
}
public abstract T TargetController { get; }
protected void TargetSetup()
{
var routes = new RouteCollection();
RouteRegistrar(routes);
var responseMock = new Mock<HttpResponseBase>();
responseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns((string url) => url);
var contextMock = new Mock<HttpContextBase>();
contextMock.SetupGet(x => x.Request).Returns(MockRequest.Object);
contextMock.SetupGet(x => x.Response).Returns(responseMock.Object);
contextMock.SetupGet(x => x.Session).Returns(Mock<HttpSessionStateBase>().Object);
TargetController.ControllerContext = new ControllerContext(contextMock.Object, new RouteData(), TargetController);
TargetController.Url = new UrlHelper(new RequestContext(contextMock.Object, new RouteData()), routes);
}
protected void DefaultRouteRegistrar(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
}
Inherit from this code and make sure you call TargetSetup() before test execution ( maybe in test initialization (setup). And you are good to go as in:
[TestClass]
public class AuctionControllerTests: TestControllerBase<AuctionController>
{
public AuctionController TargetController {
get {return new AuctionController();//inject your mocked dependencies}}
[TestInitialize]
public void SetUp()
{
TargetSetup()
}
}
So Thanks for the mocking solution, It worked but I wrote a generic better solution without using mocking framework, I'll apply mocking framework later, right now I'll stick with fakes and real dbs for integration tests.
but firstable I wrote a Generic TableController in order to apply multiple EntityData and DbContext for those who had more than one Context, also you could apply a FakeContext thanks to the abstraction of interfaces but i havent applied to this example.
First This is my BaseController:
//This is an abstract class so we can apply inheritance to scalfolded tablecontrollers<T>.
public abstract class BaseController<TModel, TDbContext> : TableController<TModel> where TModel : class, ITableData
where TDbContext:DbContext, new()
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var context = new TDbContext();
SetDomainManager(new EntityDomainManager<TModel>(context, Request));
}
public void SetDomainManager(EntityDomainManager<TModel> domainManager)
{
DomainManager = domainManager;
}
}
this is my scalfolded controller with my basecontroller applied!!!
public class AuctionController : BaseController<Auction, MobileServiceContext>
{
public IQueryable<Auction> GetAllAuction()
{
return Query();
}
// GET tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Auction> GetAuction(string id)
{
return Lookup(id);
}
// PATCH tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Auction> PatchAuction(string id, Delta<Auction> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Auction
public async Task<IHttpActionResult> PostAuction(Auction item)
{
Auction current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteAuction(string id)
{
return DeleteAsync(id);
}
}
With my generic application I can apply any DbContext that way you could even apply FakeDbContexts in order to avoid SqlConnection or Cloud connection such as Azure which is the one I used in this example.
UPDATED MARCH 14th, 2018
All this two library are on my Backend project, now I'll show you my test project in order to Unit Test a TableController
public abstract class ControllerTestBase<TController, TModel, TDbContext> where TController : BaseController<TModel, TDbContext>, new()
where TModel : class, ITableData
where TDbContext: DbContext, new()
{
protected readonly TController Controller;
protected ControllerTestBase()
{
Controller = new TController();
Controller.Configuration = new HttpConfiguration();
Controller.Request = new HttpRequestMessage();
var context = new TDbContext();
Controller.SetDomainManager(new EntityDomainManager<TModel>(context, Controller.Request));
}
}
Ok thanks to this abstract class you can supress the initialize setup from the testing library because each time you run a test it will call the generic test constructor, setting up all the necessary requierements and thus avoid ArgumentNullExceptions and InvalidOperationExceptions such common problem for unit testing tablecontroller since isnt quite intuitive to initialize as an ApiController.
Finally if you modify this then you can run a test like this:
[TestClass]
public class AuctionControllerTest : ControllerTestBase<AuctionController, Auction, MobileServiceContext>
{
[TestMethod]
public void Fetch_All_Existing_Items()
{
Assert.AreEqual(1, Controller.GetAllAuction().ToList().Count);
}
}
thanks to my generic application you can now use this code as an example to be apply to your TableControllers and also if you follow the Interface Segregation Principle you could apply FakeDbContext to your Controllers.
For those who helped me thanks you opened my mind into coming with this solution!!!
I'm new to unit testing, so my problem is probably with my code and not the Moq framework, but here goes.
I'm using .Net Core with xUnit and the Moq framework, and I'm more or less following instructions from their documentation. I'm trying to test route api/user to get all users, and the issue was on asserting that the response was an ObjectResult containing <IEnumerable<User>>. No matter what I tried, result.Value was always null. The first assertion passes fine.
I set up a console project to debug this, and found something interesting. that value of the controller in the test method in Visual Studio is null. In VS Code, the value in the debugger shows Unknown Error: 0x00000....
Below is the test:
public class UserControllerTests {
[Fact]
public void GetAll_ReturnsObjectResult_WithAListOfUsers() {
// Arrange
var mockService = new Mock<IUserService>();
var mockRequest = new Mock<IServiceRequest>();
mockService.Setup(svc => svc.GetUsers(mockRequest.Object))
.Returns(new ServiceRequest(new List<User> { new User() }));
var controller = new UserController(mockService.Object);
// Act
var result = controller.GetAll();
// Assert
Assert.IsType<ObjectResult>(result);
Assert.IsAssignableFrom<IEnumerable<User>>(((ObjectResult)result).Value);
}
}
And here is the controller:
public class UserController : Controller {
private IUserService service;
public UserController(IUserService service) {
this.service = service;
}
[HttpGet]
public IActionResult GetAll() {
var req = new ServiceRequest();
service.GetUsers(req);
if(req.Errors != null) return new BadRequestObjectResult(req.Errors);
return new ObjectResult(req.EntityCollection);
}
}
And the Service Layer:
public interface IUserService {
IServiceRequest GetUsers(IServiceRequest req);
}
public class UserService : IUserService {
private IUserRepository repo;
public IServiceRequest GetUsers(IServiceRequest req) {
IEnumerable<User> users = null;
try {
users = repo.GetAll();
}
catch(MySqlException ex) {
req.AddError(new Error { Code = (int)ex.Number, Message = ex.Message });
}
finally {
req.EntityCollection = users;
}
return req;
}
}
public interface IServiceRequest {
IEnumerable<Object> EntityCollection { get; set; }
List<Error> Errors { get; }
void AddError(Error error);
}
public class ServiceRequest : IServiceRequest {
public IEnumerable<Object> EntityCollection { get; set; }
public virtual List<Error> Errors { get; private set; }
public ServiceRequest () { }
public void AddError(Error error) {
if(this.Errors == null) this.Errors = new List<Error>();
this.Errors.Add(error);
}
}
Like I said, it's probably something I'm doing wrong, I'm thinking in the mockService.Setup() but I'm not sure where. Help please?
From the use of service.GetUsers(req) it looks like service is suppose to populate the service request but in your setup you have it returning a service request. A result which is also not used according to your code.
You need a Callback to populate whatever parameter is given to the service in order to mock/replicate when it is invoked. Since the parameter is being created inside of the method you will use Moq's It.IsAny<> to allow the mock to accept any parameter that is passed.
var mockService = new Mock<IUserService>();
mockService.Setup(svc => svc.GetUsers(It.IsAny<IServiceRequest>()))
.Callback((IServiceRequest arg) => {
arg.EntityCollection = new List<User> { new User() };
});
This should allow the method under test to flow through it's invocation and allow you to assert the outcome.
In ASP.NET 4 MVC5, I had this class that allowed me to return custom responses for unauthenticated responses to JSON endpoints. Here it is.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (IsAjax(filterContext))
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
success = false,
error = "You must be signed in."
}
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
private bool IsAjax(AuthorizationContext filterContext)
{
return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() !=
null;
}
}
However, in MVC6, the new AuthorizeAttribute is no overrides for creating custom IActionResult results. How do I do this in MVC6?
A good point has been made by #blowdart in his comment about whether returning 401/403 should be the expected behaviour. In any case, I have tried a different approach for doing what the OP was asking, modifying the behavior of the default MVC authorization filters so that we return a json when user is unauthorized.
First thing I did was creating a new IAsyncAuthorizationFilter that will format the unauthorized result as a json for ajax request. It will basically:
Wrap an existing filter
Execute the wrapped filter
In case the user is unauthorized by the wrapped filter, return a json for ajax requests
This would be the CustomJsonAuthorizationFilter class:
public class CustomJsonAuthorizationFilter : IAsyncAuthorizationFilter
{
private AuthorizeFilter wrappedFilter;
public CustomJsonAuthorizationFilter(AuthorizeFilter wrappedFilter)
{
this.wrappedFilter = wrappedFilter;
}
public async Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
await this.wrappedFilter.OnAuthorizationAsync(context);
if(context.Result != null && IsAjaxRequest(context))
{
context.Result = new JsonResult(new
{
success = false,
error = "You must be signed in."
});
}
return;
}
//This could be an extension method of the HttpContext/HttpRequest
private bool IsAjaxRequest(Microsoft.AspNet.Mvc.Filters.AuthorizationContext filterContext)
{
return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
Then I have created an IApplicationModelProvider in order to wrap all existing AuthorizeFilter with the new custom filter. The AuthroizeFilter is added by AuthorizationApplicationModelProvider, but the new provider will be run after the default one since the order of the default provider is -990.
public class CustomFilterApplicationModelProvider : IApplicationModelProvider
{
public int Order
{
get { return 0; }
}
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
//Do nothing
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
this.ReplaceFilters(context.Result.Filters);
foreach(var controller in context.Result.Controllers)
{
this.ReplaceFilters(controller.Filters);
foreach (var action in controller.Actions)
{
this.ReplaceFilters(action.Filters);
}
}
}
private void ReplaceFilters(IList<IFilterMetadata> filters)
{
var authorizationFilters = filters.OfType<AuthorizeFilter>().ToList();
foreach (var filter in authorizationFilters)
{
filters.Remove(filter);
filters.Add(new CustomJsonAuthorizationFilter(filter));
}
}
}
Finally, update ConfigureServices in startup with the new application model provider:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, CustomFilterApplicationModelProvider>());
I finally figured it out after looking at the source.
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
Func<CookieRedirectContext, Task> _old;
public CustomCookieAuthenticationEvents()
{
_old = OnRedirectToLogin;
OnRedirectToLogin = OnCustomRedirectToLogin;
}
public Task OnCustomRedirectToLogin(CookieRedirectContext context)
{
var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
if (actionContext.ActionContext == null)
return _old(context);
if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute))
{
// this is an ajax request, return custom JSON telling user that they must be authenticated.
var serializerSettings = context
.HttpContext
.RequestServices
.GetRequiredService<IOptions<MvcJsonOptions>>()
.Value
.SerializerSettings;
context.Response.ContentType = "application/json";
using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8))
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.CloseOutput = false;
var jsonSerializer = JsonSerializer.Create(serializerSettings);
jsonSerializer.Serialize(jsonWriter, new
{
success = false,
error = "You must be signed in."
});
}
}
return Task.FromResult(0);
}
else
{
// this is a normal request to an endpoint that is secured.
// do what ASP.NET used to do.
return _old(context);
}
}
}
Then, use this event class as follows:
services.Configure<IdentityOptions>(options =>
{
options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents();
});
ASP.NET 5 sure made simple things harder to do. Granted though, I can now customize things at a more granular level without effecting other pieces. Also, the source code is amazingly easy to read/understand. I am pretty happy having the confidence that any issue I am having can easily be identified as a bug or resolved by looking at the source.
Cheers to the future!
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:
Can't save a client with less than 2 characters in the name.
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
}
}