Модульное тестирование для асинхронной задачи <ActionResult> - PullRequest
0 голосов
/ 24 октября 2018

Я очень новичок в ASP.NET CORE MVC, и мне было интересно, если кто-нибудь может помочь мне в моей проблеме.

Я работал над проектом, который получит все проекты в рамках конкретной организации Azure Devops.

Вот мой код контроллера:

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

Может кто-нибудь помочь, как провести модульное тестирование с этим?Или я должен реорганизовать этот?

1 Ответ

0 голосов
/ 24 октября 2018

У вас есть путь ко многим зависимостям.

Почему сигнатура метода передает значение 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;
   }
  }
}

Надеюсь, вы используете DIFramework:

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
  {
    //...
  }

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