我对ASP.NET CORE MVC还是很陌生,我想知道是否有人可以帮助我解决我的问题。
我当时在一个项目中工作,该项目将使所有项目都归入特定的Azure组织。
这是我的控制器代码:
public async Task<ActionResult> Organization(string selectedOrg, string oauth)
{
var client = new HttpClient();
IndexViewModel model = new IndexViewModel();
model.Organizations = OrganizationData.Data;
if (selectedOrg == null)
{
selectedOrg = model.Organizations.FirstOrDefault().OrgName;
}
else
{
model.SelectedOrg = selectedOrg;
}
var token = _cache.Get<TokenModel>("Token" + HttpContext.Session.GetString("TokenGuid"));
oauth = token.AccessToken;
var url = "https://dev.azure.com/" + selectedOrg + "/_apis/projects?api-version=4.1";
try
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", oauth);
var response = await client.GetAsync(url);
var responseBody = response.Content.ReadAsStringAsync().Result;
model.Projects = JsonConvert.DeserializeObject<ProjectsModel>(responseBody);
client.Dispose();
return View("Index", model);
}
catch(Exception e)
{
client.Dispose();
return Json(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(e.ToString()));
}
}
有人可以帮助进行此单元测试吗?还是我必须重构这个?
答案 0 :(得分:1)
您有很多依赖。
为什么方法的签名传递一个从未使用过的oauth
值?
首先,应该禁止在控制器内部通过http调用任何外部依赖项。这整个事情应该抽象为它自己的调用。由于它似乎正在获取数据,因此它实际上应该在您的数据层。 我认为是很可能超出了单独项目的整个n层方法范围的范围,因此让我们只涵盖单元测试的最低要求。
首先,您需要抽象HttpClient。如果它们在外部(在大多数情况下)进行了任何调用,那么您就无法真正执行单元测试方法,因为这不是单元测试,而是集成测试。
// I don't have a full grasp of your complete eco-system so based on the
// minimal information provided, this would at least get you close
public interface IAzureAPI
{
public Task<string> GetOrgAsync(string org, string oauth);
}
public class AzureAPI : IDisposable
{
public async Task<string> GetOrgAsync(string org, string oauth)
{
// use *using* not try/catch/finally/dispose
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", oauth);
var url = "https://dev.azure.com/" + org+ "/_apis/projects?api-version=4.1";
var response = await client.GetAsync(url);
// never use `.Result` unless you absolutely know what you are doing
// always using async/wait if possible
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
}
希望您正在使用DI框架:
public class MyController
{
private IAzureAPI _azureAPI;
public MyController(IAzureAPI azureAPI)
{
_azureAPI = azureAPI;
}
}
现在进入最困难的部分:
public async Task<ActionResult> Organization(string selectedOrg, string oauth)
{
IndexViewModel model = new IndexViewModel();
// I have no idea where model came from so
// this appears to block "unit-testing"
// strange that you don't validate `selectedOrg`, you just use it
model.Organizations = OrganizationData.Data;
if (selectedOrg == null)
{
selectedOrg = model.Organizations.FirstOrDefault().OrgName;
}
else
{
model.SelectedOrg = selectedOrg;
}
// no idea where `_cache` came from so
// also appears to block "unit-testing"
// As does `HttpContext` because you aren't using the
// Interface
var token = _cache.Get<TokenModel>("Token" + HttpContext.Session.GetString("TokenGuid"));
oauth = token.AccessToken;
try
{
var orgInfo = await _azureAPI.GetOrgAsync(selectedOrg, oauth);
model.Projects = JsonConvert.DeserializeObject<ProjectsModel>(orgInfo);
// return a view here???
return View("Index", model);
}
catch(Exception e)
{
// return JSON here instead????
return Json(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(e.ToString()));
}
}
这是一个总的开始,但是有太多的未知数,并且有太多依赖关系无法实际编写真实的单元测试。这是根据您提供的信息进行的快速结构和半测试。
public MyControllerTests
{
// for 100% Cover Coverage you'd need all of these
public async Task Organization_OrgAsString_ReturnsView
{
//...
}
public async Task Organization_OrgAsNull_ReturnsView
{
// Arrange
var azureAPI = Substitute.For<IAzureAPI>();
azureAPI.GetOrgAsync(null, null)
.Returns("somestring");
var controller = new MyController(azureAPI);
// Act
var result = await controller.Organization(null, null);
// Assert
Assert.That(result....);
}
public async Task Organization_WithException_ReturnsJson
{
//...
}
}