Arayn
Arayn

Reputation: 1106

Moq Unit Test case - ASP.NET MVC with WebAPI

I am trying to UnitTest my MVC Controller method, which internally makes call to an WebAPI(using HttpClient). I'm not able to figure out how can I fake the httpclient call, as it should not go for actual request. Below is my source code and unit test case. 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)

Base MVC Controller

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

Derived class 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 = "[email protected]";
    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);
}   

Upvotes: 2

Views: 916

Answers (2)

Russell Horwood
Russell Horwood

Reputation: 1044

I would inject an IHttpClient interface, and in release register a HttpClient wrapper that implements that interface.

Upvotes: 0

Nkosi
Nkosi

Reputation: 247531

Tight coupling to HttpClient in the base controller makes it difficult to test derived classes in isolation. Review and refactor that code to follow DI.

No need to have a base controller and it is usually not advised.

Extract PostRequestAsync out into its own service abstraction and implementation.

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

Refactor derived controllers to depend on the service abstraction

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

This should now allow you to mock the dependency when testing in isolation without adverse side effects.

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

    var mockModel = new LoginViewModel { };
    mockModel.Password = "TestPassword";
    mockModel.Email = "[email protected]";
    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
}

Upvotes: 3

Related Questions