我有一个ASP.NET Web应用程序,并且想为主控制器编写一些UT。
大多数控制器方法都是与UI的交互。例如,无论何时加载主页面,它都会调用控制器通过API调用从数据库中获取所有用户。
此方法是控制器中获取所有用户的方法。
public JsonResult GetAllUsers()
{
List<User> users = null;
try
{
users = new List<User>();
var allUsersJsonResults = Requests.GetFromUri(Settings.AllUsersUri);
users = JsonConvert.DeserializeObject<List<User>>(allUsersJsonResults.ToString());
return Json(new
{
usersDetails = users,
errorMessage = ""
}, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new
{
usersDetails = users,
errorMessage = "Error loading users"
}, JsonRequestBehavior.AllowGet);
}
}
这是实际调用API的方法
public static string GetFromUri(string fullUri)
{
HttpClientHandler handler = new HttpClientHandler
{
Credentials = Settings.ServiceCredential
};
using (HttpClient client = new HttpClient(handler))
{
return client.GetStringAsync(fullUri).Result;
}
}
最后,这就是User
类的样子
public class User
{
public int UserId {get; set;}
public string UserName {get; set;}
public string UserLastName {get; set;}
public int UserAge {get; set;}
public int UserNationality {get; set;}
}
此时我要做的是编写一些单元测试来验证GetAllUsers()
电话,但我不知道该怎么做。我想我不应该同时调用Test或者生产API调用,因为它们可能会不时返回完全不同的数据并使单元测试失败,但我不知道如何1)测试控制器而不调用服务器API和2 )模拟一些数据来模拟答案。
答案 0 :(得分:3)
GetFromUri 是 静态 方法。结果,模拟单元测试并不容易。
理想情况下,我们希望将 接口 注入 控制器 。
我们需要返回强类型类 UserResponse 而不是匿名,以便我们可以在单元测试中转换回原始类型。
public class UsersController : Controller
{
private readonly IRequests _requests;
public UsersController(IRequests requests)
{
_requests = requests;
}
public JsonResult GetAllUsers()
{
List<User> users = null;
try
{
users = new List<User>();
var allUsersJsonResults = _requests.GetFromUri(Settings.AllUsersUri);
users = JsonConvert.DeserializeObject<List<User>>(allUsersJsonResults);
return Json(new UserResponse { usersDetails = users, errorMessage = "" },
JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(users, JsonRequestBehavior.AllowGet);
}
}
}
仅供参考:请重命名这些班级名称。类名应该是单数名词。
public interface IRequests
{
string GetFromUri(string fullUri);
}
public class Requests : IRequests
{
public string GetFromUri(string fullUri)
{
HttpClientHandler handler = new HttpClientHandler
{
Credentials = Settings.ServiceCredential
};
using (HttpClient client = new HttpClient(handler))
{
return client.GetStringAsync(fullUri).Result;
}
}
}
public class Settings
{
public static ICredentials ServiceCredential { get; set; }
public static string AllUsersUri { get; set; }
}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserResponse
{
public IList<User> usersDetails { get; set; }
public string errorMessage { get; set; }
}
我将NUnit与NSubstitute一起使用。您可以轻松替换任何您喜欢的模拟框架。
[TestFixture]
public class UserControllerTest
{
private User _user1;
private User _user2;
private IList<User> _users;
[SetUp]
public void SetUp()
{
_user1 = new User { FirstName = "John", LastName = "Doe" };
_user2 = new User { FirstName = "Janet", LastName = "Doe" };
_users = new List<User> { _user1, _user2 };
}
[Test]
public void GetAllUsers_ReturnUsers()
{
// Arrange
var requests = Substitute.For<IRequests>();
requests.GetFromUri(Arg.Any<string>()).Returns(JsonConvert.SerializeObject(_users));
var sut = new UsersController(requests);
// Act
var result = sut.GetAllUsers() as JsonResult;
// Assert
Assert.IsNotNull(result);
Assert.IsNotNull(result.Data);
var users = result.Data as UserResponse;
Assert.AreEqual(users.usersDetails[0].FirstName, _users[0].FirstName);
Assert.AreEqual(users.usersDetails[1].FirstName, _users[1].FirstName);
}
}
答案 1 :(得分:1)
操作和扩展控制器与实现问题紧密耦合。尽量保持控制器的精益。摘要接口依赖性背后的实现,以便可以单独进行模拟和测试。
当前设计中的动作与静态依赖关系紧密耦合,这使得测试变得困难。
使用以下
public interface IUsersService {
GetAllUsersResponse GetAllUsers();
}
public class GetAllUsersResponse {
public IList<User> usersDetails { get; set; }
public string errorMessage { get; set; }
}
以下是使用抽象重构的控制器操作的示例。
public class MainController : Controller {
private IUsersService usersService;
public MainController(IUsersService service) {
this.usersService = service;
}
public JsonResult GetAllUsers() {
var response = usersService.GetAllUsers();
return Json(response, JsonRequestBehavior.AllowGet);
}
}
对该行动进行更简单的单元测试。 (注意:使用 Moq 模拟依赖项)
[TestMethod]
public void GetAllUsers_Should_Call_Service() {
//Arrange
var mock = new Mock<IUsersService>();
var response = new GetAllUsersResponse {
usersDetails = new List<User>(),
errorMessage = string.Empty,
};
mock.Setup(m => m.GetAllUsers()).Returns(response);
var controller = new MainController(mock.Object);
//Act
var jsonResult = controller.GetAllUsers();
//Assert
mock.Verify(m => m.GetAllUsers(), Times.AtLeastOnce());
}
尽管所有失败的当前行动都可以被封装在抽象之后。