如何对这个ASP.NET Web App Controller进行单元测试?

时间:2017-03-17 21:27:08

标签: c# asp.net asp.net-mvc unit-testing

我有一个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 )模拟一些数据来模拟答案。

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);
        }
    }
}

IRequests和Requests

仅供参考:请重命名这些班级名称。类名应该是单数名词。

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());
}

尽管所有失败的当前行动都可以被封装在抽象之后。