我正在尝试对我的MVC Controller方法进行UnitTest,该方法在内部调用WebAPI(使用HttpClient)。我无法弄清楚如何伪造httpclient调用,因为它不应用于实际请求。下面是我的源代码和单元测试用例。测试用例失败,因为实际的HttpRequest调用(发送请求时发生错误。无法建立与服务器的连接)
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;
}
}
}
}
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);
}
答案 0 :(得分:2)
与基本控制器中的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
}
答案 1 :(得分:0)
我将注入一个IHttpClient接口,并在发布时注册一个实现该接口的HttpClient包装器。