Контроллер тестирования интеграции C # с привязкой FromForm к свойству IFormFile - PullRequest
1 голос
/ 23 июня 2019

Я использую:

  • Visual Studio 2017 Pro
  • dotnet core sdk 2.2.102
  • XUnit 2.4.1

Что я пытаюсь сделать

Интеграционный тест метода контроллера API, который принимает данные формы.

Настройка

  • API-маршрут в моем контроллере, который принимает CommandObject с использованием атрибута [FromForm]
  • Одно из свойств CommandObject имеет тип List<IFormFile>, оно предназначено для управления любыми файлами, которые являются частью запроса
  • Метод Controller работает как положено, когда я тестирую его вручную из Почтальона.

Проблема

Файлы не привязаны к свойству List<IFormFile>. Все остальное работает как положено, а файлы - нет. Я впервые использую Multipart Form Data, поэтому не уверен, что попробовать.

Когда я отлаживаю тест, вы можете видеть, что все работает, за исключением свойства Documents (обратите внимание, что это не соответствует 100% с кодом ниже, потому что я должен был запутать некоторые вещи)

enter image description here

Материал, на который я смотрел

Существует много вещей, относящихся к данным многокомпонентных форм, некоторые решения, которые я пробовал:

MyIntegrationTest.cs

За настройкой моих интеграционных тестов написано много кода. Если я размещу все это здесь, я не думаю, что это будет очень полезно. Наиболее важной частью информации является то, что переменная server имеет тип Microsoft.AspNetCore.TestHost.TestServer

[Fact]
async Task Post_ItemAsync_HappyPath_ReturnsOKStatusCode()
{
    var fileDir = @"C:/path/to/files";
    var fileNames = new string[] { "test.docx", "test.txt" };

    using (var server = CreateTestServer())
    {
        // Arrange
        var formData = new MultipartFormDataContent()
        {
            { new StringContent("Test Title"), "Title" },
            { new StringContent("Test Description"), "Description" },
            { new StringContent("String_1"), "AListOfStrings" },
            { new StringContent("String_2"), "AListOfStrings" },
            { new StringContent("3"), "NumberOfThings" }
        };

        foreach (var fileName in fileNames)
        {
            var document = File.ReadAllBytes($"{fileDir}/{fileName}");
            formData.Add(new ByteArrayContent(document), "file", fileName);
        }

        string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
        string contentType = "multipart/form-data; boundary=" + formDataBoundary;

        var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/item")
        {
            Headers =
            {
                { HttpRequestHeader.ContentType.ToString(), contentType }
            },
            Content = formData
        };

        // Act
        var response = await server.CreateClient().SendAsync(request);

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        // Cleanup
        ...
    }
}

MyController.cs

[HttpPost]
ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> CreateItemAsync([FromForm]CreateItemCommand command)
{
    bool commandResult = false;

    commandResult = await _mediator.Send(command);

    if (!commandResult)
    {
        return BadRequest();
    }

    return Ok();
}

CreateItemCommand.cs

[DataContract]
public class CreateItemCommand
    :IRequest<bool>
{
    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Description { get; set; }

    [DataMember]
    public HashSet<string> AListOfThings { get; set; }

    [DataMember]
    public int NumberOfThings { get; set; }

    [DataMember]
    public List<IFormFile> Documents { get; private set; }

    public CreateITemCommand()
    {
        AListOfThings = new HashSet<string>();
    }

    public CreateItemCommand(string title, string description, HashSet<string> aListOfThings, int NumberOfThings, List<IFormFile> documents)
        : this()
    {
        Title = title;
        Description = description;
        AListOfStrings = aListOfStrings;
        NumberOfThings = numberOfThings;
        Documents = documents;
    }
}

1 Ответ

1 голос
/ 23 июня 2019

Граница данных формы должна быть добавлена ​​к MultipartFormDataContent после инициализации, а имена файлов должны соответствовать желаемому свойству модели для заполнения.

//...

// Arrange
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());

var formData = new MultipartFormDataContent(formDataBoundary) { //<---- NOTE HERE
    { new StringContent("Test Title"), "Title" },
    { new StringContent("Test Description"), "Description" },
    { new StringContent("String_1"), "AListOfStrings" },
    { new StringContent("String_2"), "AListOfStrings" },
    { new StringContent("3"), "NumberOfThings" }
};

foreach (var fileName in fileNames) {
    var document = File.ReadAllBytes($"{fileDir}/{fileName}");
    formData.Add(new ByteArrayContent(document), "Documents", fileName); //<-- NOTE HERE
}

// Act
var response = await server.CreateClient().PostAsync("api/v1/item", formData);

//...

Свойство модели Documents должно быть установлено как общедоступное, чтобы связыватель модели мог заполнять его при анализе данных формы.

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