Reputation: 1187
I have this method inside a controller:
[HttpPatch]
[AllowAdminOnly]
public JsonResult EditUser(User _user)
{
try
{
if (ModelState.IsValid)
{
_user.Edit();
}
else
{
string error_messages = "";
foreach (var e in ModelState.Select(x => x.Value.Errors).Where(y => y.Count > 0).ToList())
{
error_messages += e[0].ErrorMessage + "\n";
}
throw new Exception(error_messages);
}
return MessageHelper(@Resources.Global.success, @Resources.Users.success_editing_user, "success");
}
catch (Exception err)
{
return ErrorHelper(@Resources.Global.error, @Resources.Users.error_editing_user, err.Message);
}
}
In order to unit test this method, I have found references (...) saying that I should "mock" the _user.Edit();
. It seems fine, it will avoid saving data to database and will make the tests faster.
My test then (for a valid user) becomes:
[TestMethod]
public void UserController_EditUser_Valid()
{
// Arrange
FundController controller = new UserController();
controller.ControllerContext = TestModelHelper.AdminControllerContext();
var _user = new Mock<Fund>();
_user.SetupGet(f => f.id).Returns(1);
_user.SetupGet(f => f.name).Returns("User name");
_user.SetupGet(f => f.nickname).Returns("User nickname");
_user.SetupGet(f => f.active).Returns(true);
_user.Setup(f => f.Edit()).Callback(() => {}).Verifiable();
// Act
var result = (JsonResult)controller.EditUser(_user.Object);
SimpleMessage resultMessage = m_serializer.Deserialize<SimpleMessage>(m_serializer.Serialize(result.Data));
// Assert
Assert.IsNotNull(resultMessage, "Result must not be null");
Assert.IsTrue(resultMessage.status.Equals("success"), "status must be 'success'");
}
But when I do this, I get the following error:
Test Name: UserController_EditUser_Valid
Test FullName: Pmanager.Tests.Controllers.UserControllerTest.UserController_EditUser_Valid
Test Source: ...\Pmanager\Pmanager.Tests\Controllers\UserControllerTest.cs : line 95
Test Outcome: Failed
Test Duration: 0:00:00,0179908
Result StackTrace:
em Moq.Mock.ThrowIfCantOverride(Expression setup, MethodInfo method)
em Moq.Mock.<>c__DisplayClass66_0`2.<SetupGet>b__0()
em Moq.PexProtector.Invoke[T](Func`1 function)
em Moq.Mock.SetupGet[T,TProperty](Mock`1 mock, Expression`1 expression, Condition condition)
em Moq.Mock`1.SetupGet[TProperty](Expression`1 expression)
em Pmanager.Tests.Controllers.UserControllerTest.UserController_EditUser_Valid() na ...\Pmanager\Pmanager.Tests\Controllers\UserControllerTest.cs:linha 100
Result Message:
Test method Pmanager.Tests.Controllers.UserControllerTest.UserController_EditUser_Valid threw exception:
System.NotSupportedException: Valid setup on a non-virtual (overridable in VB) member: f => f.id
I found some documentation about (...) saying that I should use an interface instead of the class when creating the "mock".
So, I created an interface:
public interface IUser
{
int id { get; set; }
string name { get; set; }
string nickname { get; set; }
bool active { get; set; }
void Edit();
}
and then I changed everything for IUser
, the controller's method signature:
public JsonResult EditUser(IUser _user);
the test "mock" declaration:
var _user = new Mock<IUser>();
and so on.
Now the test works, but the actual controller's method for editing the user doesn't!
How can I put all these things together without breaking the controller trully functionallity?
Upvotes: 2
Views: 960
Reputation: 247423
Update controller to use a dependency to update the user model. Remove that functionality from the model itself. Models should be POCOs/DTOs.
public class UserController : Controller {
readonly IUserService userService;
public UserController(IUSerService userService) {
this.userService = userService;
}
[HttpPatch]
[AllowAdminOnly]
public JsonResult EditUser(User _user) {
try {
if (ModelState.IsValid) {
userService.Edit(user);
} else {
string error_messages = "";
foreach (var e in ModelState.Select(x => x.Value.Errors).Where(y => y.Count > 0).ToList()) {
error_messages += e[0].ErrorMessage + "\n";
}
throw new Exception(error_messages);
}
return MessageHelper(@Resources.Global.success, @Resources.Users.success_editing_user, "success");
} catch (Exception err) {
return ErrorHelper(@Resources.Global.error, @Resources.Users.error_editing_user, err.Message);
}
}
}
where the IUserService
is something like
public interface IUserService {
void Edit(User user);
}
and its production implementation would perform the desired action. Remember to register abstraction and implementation with what ever DI you are using.
The test would then mock the dependencies needed for it to run in isolation.
[TestMethod]
public void UserController_EditUser_Should_Be_Valid() {
// Arrange
var _user = new User {
id = 1,
name = "User name",
nickname = "User nickname",
active = true
};
var mockService = new Mock<IUserService>();
mockService .Setup(m => m.Edit(_user)).Verifiable();
var controller = new UserController(mockService.Object);
controller.ControllerContext = TestModelHelper.AdminControllerContext();
// Act
var result = controller.EditUser(_user) as JsonResult;
// Assert
Assert.IsNotNull(result, "Result must not be null");
mockService.Verify(); // verify that the service was call successfully.
}
Upvotes: 2
Reputation: 1187
Well, I am still in doubt about the correct way of doing all this.
But I figured a way of making both working. It is:
public JsonResult EditUser(User _user);
var result = (JsonResult)controller.EditUser(_user.Object as User);
Although it works, I am not sure whether the whole stuff is in harmony... :(
Hope someone could give a better answer.
Upvotes: 0