我遵循了水槽教程来创建测试,但它缺少一个我急需的示例;我无法使用我的控制器测试文件上传端点。
我已经实现了一个这样的控制器:
class FileController extends ResourceController {
FileController() {
acceptedContentTypes = [ContentType("multipart", "form-data")];
}
@Operation.post()
Future<Response> postForm() async {
final transformer = MimeMultipartTransformer(request.raw.headers.contentType.parameters["boundary"]);
final bodyStream = Stream.fromIterable([await request.body.decode<List<int>>()]);
final parts = await transformer.bind(bodyStream).toList();
for (var part in parts) {
final headers = part.headers;
HttpMultipartFormData multipart = HttpMultipartFormData.parse(part);
final content = multipart.cast<List<int>>();
final filePath = "uploads/test.txt";
await new File(filePath).create(recursive: true);
IOSink sink = File(filePath).openWrite();
await content.forEach(sink.add);
await sink.flush();
await sink.close();
}
return Response.ok({});
}
}当使用Postman上传文件时,它工作得很好。
现在,我正在尝试为这个端点编写一个测试:
test("POST /upload-file uploads a file to the server", () async {
final file = File('test.txt');
final sink = file.openWrite();
sink.write('test');
await sink.close();
final bytes = file.readAsBytesSync();
harness.agent.headers['Content-Type'] = 'multipart/form-data; boundary=MultipartBoundry';
harness.agent.headers['Content-Disposition'] = 'form-data; name="file"; filename="test.txt"';
final response = await harness.agent.post("/upload-file", body: bytes);
expectResponse(response, 200);
});并在vscode调试器中获取以下内容:
Expected: --- HTTP Response ---
- Status code must be 200
- Headers can be anything
- Body can be anything
---------------------
Actual: TestResponse:<-----------
- Status code is 415
- Headers are the following:
- x-frame-options: SAMEORIGIN
- x-xss-protection: 1; mode=block
- x-content-type-options: nosniff
- server: aqueduct/1
- content-length: 0
- Body is empty
-------------------------
>
Which: Status codes are different. Expected: 200. Actual: 415发布于 2019-11-15 03:55:00
415状态码响应将指示ResourceController已经拒绝了请求的内容类型。您已经正确地设置了acceptedContentTypes,但是,隐藏在Agent.headers文档中的测试代理之间存在细微差别(诚然令人困惑
Default headers to be added to requests made by this agent.
By default, this value is the empty map.
Do not provide a 'content-type' key. If the key 'content-type' is present, it will be removed prior to sending the request. It is replaced by the value of TestRequest.contentType, which also controls body encoding.
See also setBasicAuthorization, bearerAuthorization, accept, contentType for setting common headers.请参阅接口参考here。至于为什么这样存在:与您的响应一样,TestRequest (当您使用代理发出请求时创建和执行的对象)的内容类型决定了将CodecRegistry中的哪个编解码器用作编码器。这允许您始终处理'Dart对象‘,并让水渠处理编码/解码。
发布于 2020-05-25 19:59:08
我写了一堆类来简化和澄清多部分请求测试。因此,如果有人还在为此而苦苦挣扎,欢迎尝试我的解决方案:
测试
import 'multipart_body_parser.dart';
//[...]
test('POST /upload-file uploads a file to the server', () async {
final boundary = '7d82a244f2ea5xd0s046';
final file = File('test.txt');
var encodedBody = MultipartBodyParser(boundary).parse([
FileBodyPart(
'file',
'test.txt',
File('test.txt'),
),
]);
final response = await harness.agent.post(
'/upload-file',
body: encodedBody,
);
expectResponse(response, 200);
});multipart_body_parser.dart
import 'dart:convert';
import 'dart:io';
class MultipartBodyParser {
final String boundary;
MultipartBodyParser(this.boundary)
: assert(
boundary != null,
'The boundary is empty. Please set it ' +
'and keep on mind that it MUST NOT appear inside any of the ' +
'encapsulated parts. Example: "sampleBoundary7da24f2e50046".',
);
List<int> get encodedNonLastBoundary =>
ascii.encode('\r\n--' + boundary + '\r\n');
List<int> get encodedLastBoundary =>
ascii.encode('\r\n--' + boundary + '--\r\n\r\n');
List<int> parse(List<_BodyPart> parts) {
if (parts == null || parts.isEmpty) {
throw MultipartBodyParserException(
'Parts CAN NOT be empty. Please set at least one part of body.',
);
}
var body = encodedNonLastBoundary;
parts.forEach((part) {
body += part.parse();
if (parts.last != part) {
body += encodedNonLastBoundary;
}
});
body += encodedLastBoundary;
return body;
}
}
class TextBodyPart extends _BodyPart {
final String content;
TextBodyPart(formFieldName, _content)
: content = _content ?? '',
super(
_ContentDisposition(
formFieldName,
'form-data',
),
_ContentType(),
);
@override
List<int> get encodedContent => ascii.encode(content);
}
class FileBodyPart extends _BodyPart {
final File file;
final String fileName;
FileBodyPart(formFieldName, this.fileName, this.file)
: super(
_ContentDisposition(
formFieldName,
'form-data',
'; filename="$fileName"',
),
_ContentType('application/octet-stream'),
);
@override
List<int> get encodedContent => file.readAsBytesSync();
}
abstract class _BodyPart {
final _ContentDisposition contentDisposition;
final _ContentType contentType;
_BodyPart(this.contentDisposition, this.contentType)
: assert(contentDisposition != null),
assert(contentType != null);
String get partHeader =>
contentDisposition.toString() + contentType.toString();
List<int> get encodedContent;
List<int> parse() => ascii.encode(partHeader) + encodedContent;
}
class _ContentDisposition {
final String formFieldName;
final String formFieldType;
final String additionalParams;
_ContentDisposition(this.formFieldName, [_formFieldType, _additionalParams])
: formFieldType = _formFieldType ?? 'form-data',
additionalParams = _additionalParams ?? '',
assert(formFieldName != null);
@override
String toString() =>
'content-disposition: $formFieldType; name="$formFieldName"$additionalParams\r\n';
}
class _ContentType {
final String type;
_ContentType([this.type = 'text/plain']) : assert(type != null);
@override
String toString() => 'content-type: $type\r\n\r\n';
}
class MultipartBodyParserException implements Exception {
final String message;
const MultipartBodyParserException([this.message]);
}https://stackoverflow.com/questions/58839496
复制相似问题