Модульный тестовый пример Moq - ASP.NET MVC с WebAPI - PullRequest
0 голосов
/ 30 августа 2018

Я пытаюсь выполнить UnitTest мой метод контроллера MVC, который внутренне выполняет вызов WebAPI (используя HttpClient). Я не могу понять, как я могу подделать вызов httpclient, так как он не должен идти на фактический запрос. Ниже мой исходный код и пример модульного тестирования. Тестовый случай не пройден, так как вызов идет для фактического запроса HttpRequest (при отправке запроса произошла ошибка. Не удалось установить соединение с сервером)

Базовый контроллер MVC

public class BaseController : Controller
{
    public virtual async Task<T> PostRequestAsync<T>(string endpoint, Object obj)  where T : class 
        {

            var address = "http://localhost:5001/api/Login";
            StringContent json = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "");
            using (var client = new HttpClient())
            {
                try
                {
                    var response = await client.PostAsync(address, json); // Test case fails here
                    if (response.IsSuccessStatusCode)
                    {
                        string data = await response.Content.ReadAsStringAsync();
                        return JsonConvert.DeserializeObject<T>(data);
                    }

                    return default(T); 
                }
                catch (WebException)
                {
                    throw;
                }
            }
         }
}

Производный класс Controller

public class AccountController : BaseController
{
    public AccountController() : base()
    {

    }

    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
        if (ModelState.IsValid)
        {

            var result = await PostRequestAsync<ResultObject>(Constants.UserLoginAPI, model); // this is call for basecontroller method which actually has HttpClient call.

            var output = JsonConvert.DeserializeObject<UserObject>(result.Object.ToString());


            if (result.Succeeded && !string.IsNullOrEmpty(output.Email))
            {
                var userRoleInfo = await GetRequestAsync<List<UserRoleObject>>(string.Format(Constants.GetUserRoleInfoAPI, output.Email));

                if (userRoleInfo != null)
                {
                    var claims = new List<Claim>
                    {
                        new Claim(ClaimTypes.Name, output.Email),
                        new Claim("Username", output.UserName)

                    };

                    var claimsIdentity = new ClaimsIdentity(
                        claims, CookieAuthenticationDefaults.AuthenticationScheme);

                    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties { IsPersistent = model.RememberMe });                      

                }
                return View(new LoginViewModel());
            }

        }      
        return View(model);
    }
}   

TestMethod

[Fact]
public async Task LoginTest_Post_UserHasInValidCredentials()
{
    // Arrange

    var mockModel = new LoginViewModel { };
    mockModel.Password = "TestPassword";
    mockModel.Email = "test@test.com";
    mockModel.RememberMe = false;


    var commonResult = new CommonResult { Object = null, Succeeded = false, StatusCode = Common.Enums.ResponseStatusCodeEnum.Success };
    var email = string.Empty;

    var mockHttp = new MockHttpMessageHandler();

    var mockBase = new Mock<BaseController>() {  CallBase=true};

    mockHttp.When("http://127.0.0.1:5001/*").Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON - using RichardSzalay.MockHttp;

    //// Inject the handler or client into your application code
    StringContent jsonInput = new StringContent(JsonConvert.SerializeObject(mockModel), Encoding.UTF8, "application/json");
    var client = new HttpClient(mockHttp);
    var response = await client.PostAsync("http://127.0.0.1:5001" + Constants.UserLoginAPI, jsonInput);
    var json = await response.Content.ReadAsStringAsync();            
    mockBase.Setup(test => test.PostRequestAsync<CommonResult>(Constants.UserLoginAPI, mockModel)).Returns(Task.FromResult(CommonResult()));

    var result = await accountController.Login(mockModel); // test case fails, as the call goes for actual HttpRequest (An error occurred while sending the request. A connection with the server could not be established)
    //var viewResult = Assert.IsType<ViewResult>(result);

    Assert.NotNull(commonResult);
    Assert.False(commonResult.Succeeded);
    Assert.Empty(email);
    //Assert.NotNull(model.Email);
}   

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Я бы вставил интерфейс IHttpClient и в выпуске зарегистрировал оболочку HttpClient, которая реализует этот интерфейс.

0 голосов
/ 30 августа 2018

Плотное соединение с HttpClient в базовом контроллере затрудняет тестирование производных классов изолированно. Просмотрите и реорганизуйте этот код в соответствии с DI.

Нет необходимости иметь базовый контроллер, и обычно это не рекомендуется.

Извлечение PostRequestAsync в собственную абстракцию и реализацию сервиса.

public interface IWebService {
    Task<T> PostRequestAsync<T>(string endpoint, Object obj) where T : class;
    Task<T> GetRequestAsync<T>(string endpoint) where T : class;
}

public class WebService : IWebService {
    static HttpClient client = new HttpClient();

    public virtual async Task<T> PostRequestAsync<T>(string requestUri, Object obj) where T : class {
        var content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "");
        try {
            var response = await client.PostAsync(requestUri, content); // Test case fails here
            if (response.IsSuccessStatusCode) {
                string data = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<T>(data);
            }
            return default(T);
        } catch (WebException) {
            throw;
        }
    }

    public async Task<T> GetRequestAsync<T>(string requestUri) where T : class {
        try {
            var response = await client.GetAsync(requestUri);
            if (response.IsSuccessStatusCode) {
                string data = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<T>(data);
            }
            return default(T);
        } catch (WebException) {
            throw;
        }
    }
}

Рефакторизация производных контроллеров в зависимости от сервисной абстракции

public class AccountController : Controller {
    private readonly IWebService webService;

    public AccountController(IWebService webService) {
        this.webService = webService;
    }

    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) {
        if (ModelState.IsValid) {
            var result = await webService.PostRequestAsync<ResultObject>(Constants.UserLoginAPI, model);

            if (result.Succeeded) {
                var output = JsonConvert.DeserializeObject<UserObject>(result.Object.ToString());
                if (output != null && !string.IsNullOrEmpty(output.Email)) {
                    var userRoleInfo = await webService.GetRequestAsync<List<UserRoleObject>>(string.Format(Constants.GetUserRoleInfoAPI, output.Email));

                    if (userRoleInfo != null) {
                        var claims = new List<Claim>
                        {
                            new Claim(ClaimTypes.Name, output.Email),
                            new Claim("Username", output.UserName)

                        };

                        var claimsIdentity = new ClaimsIdentity(
                            claims, CookieAuthenticationDefaults.AuthenticationScheme);

                        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties { IsPersistent = model.RememberMe });

                    }
                    return View(new LoginViewModel());
                }
            }

        }
        return View(model);
    }
}

Теперь это должно позволить вам смоделировать зависимость при тестировании в изоляции без неблагоприятных побочных эффектов.

[Fact]
public async Task LoginTest_Post_UserHasInValidCredentials() {
    // Arrange

    var mockModel = new LoginViewModel { };
    mockModel.Password = "TestPassword";
    mockModel.Email = "test@test.com";
    mockModel.RememberMe = false;

    var commonResult = new CommonResult {
        Object = null,
        Succeeded = false,
        StatusCode = Common.Enums.ResponseStatusCodeEnum.Success
    };

    var mockWebService = new Mock<IWebService>();
    var accountController = new AccountController(mockWebService.Object) {
        //HttpContext would also be needed
    };

    mockWebService
        .Setup(test => test.PostRequestAsync<CommonResult>(Constants.UserLoginAPI, mockModel))
        .ReturnsAsync(commonResult);

    //

    //Act
    var result = await accountController.Login(mockModel);

    //Assert
    //...Make assertions here
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...