2 votes

Cas de test unitaire Moq - ASP.NET MVC avec WebAPI

J'essaie de tester à l'unité la méthode de mon contrôleur MVC, qui fait appel en interne à une WebAPI (en utilisant HttpClient). Je n'arrive pas à comprendre comment je peux simuler l'appel au httpclient, car il ne doit pas être utilisé pour une requête réelle. Vous trouverez ci-dessous mon code source et mon scénario de test unitaire. Le scénario de test échoue, car l'appel s'effectue pour une requête HttpRequest réelle (Une erreur s'est produite lors de l'envoi de la requête. Une connexion avec le serveur n'a pas pu être établie)

Contrôleur MVC de base

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

Classe dérivée 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);
}

3voto

Nkosi Points 95895

Couplage étroit avec HttpClient dans le contrôleur de base rend difficile de tester les classes dérivées de manière isolée. Révisez et remaniez ce code afin de respecter le DI.

Il n'est pas nécessaire d'avoir un contrôleur de base et ce n'est généralement pas conseillé.

Extrait PostRequestAsync dans sa propre abstraction de service et son implémentation.

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

Refactoriser les contrôleurs dérivés pour qu'ils dépendent de l'abstraction de service.

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

Cela devrait maintenant vous permettre de simuler la dépendance lors de tests isolés sans effets secondaires indésirables.

[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
}

0voto

Russell Horwood Points 184

J'injecterais une interface IHttpClient et, dans la version, j'enregistrerais un wrapper HttpClient qui implémente cette interface.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X