Flutter本地模拟服务器插件mock_web_server的使用

Flutter本地模拟服务器插件mock_web_server的使用

mock_web_server 是一个灵活的 Dart Web 服务器,可以用于编写测试脚本和与 Web 服务器的交互。

概述

通过 mock_web_server,您可以轻松地进行集成测试或重现特定的边缘情况。它允许您编写软件正在测试中的交互,以模拟 Web 服务器的行为。

MockWebServer 基于 Square 公司为 Java 创建的同名库。

使用方法

启动服务器

MockWebServer 可以运行在指定的端口或系统选择的临时端口上。

// 将使用系统选择的临时端口
new MockWebServer();

// 将使用端口 8081
new MockWebServer(port: 8081);

启动服务器只需执行以下操作:

MockWebServer server = new MockWebServer();
server.start();

一旦服务器运行起来,您可以获取其 URL 以便配置您的应用程序连接到该服务器。

server.url; // 返回 http://127.0.0.1:8081
server.port; // 8081
server.host; // 127.0.0.1

添加响应到队列

服务器启动后,您可以将响应添加到队列中。队列遵循先进先出(FIFO)原则。

// 添加空体并设置响应码为 401
server.enqueue(httpCode: 401);

// 设置响应码为 200,并添加 JSON 作为响应体
server.enqueue(body: '{ "message" : "hi"}');

// HTTP 200 响应,空体及一些头部信息
Map<String, String> headers = new Map();
headers["X-Server"] = "MockDart";
server.enqueue(headers: headers);

// 所有参数都是可选的,可以根据需要组合使用
server.enqueue(httpCode: 201, body: "answer", headers: headers, duration: duration);

// 可以直接调用 enqueueResponse() 方法来添加 MockResponse
Map<String, String> headers = new Map();
headers["X-Server"] = "MockDart";

var mockResponse = new MockResponse()
  ..httpCode = 201
  ..body = "Created"
  ..headers = headers
  ..delay = new Duration(seconds: 2);

server.enqueueResponse(mockResponse);

延迟响应

为了测试超时或竞态条件,您可以让服务器延迟响应。

server.enqueue(delay: new Duration(seconds: 2), httpCode: 201);
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();

HttpClientResponse response = request(path: "");

stopwatch.stop();
expect(stopwatch.elapsed.inMilliseconds, greaterThanOrEqualTo(2000));
expect(response.statusCode, 201);

验证请求是否正确

您可以检查应用程序是否发送了正确的请求。可以通过获取服务器上的请求来实现,请求队列遵循后进先出(LIFO)原则。

server.enqueue(body: "a");
server.enqueue(body: "b");
server.enqueue(body: "c");

request(path: "first");
request(path: "second");
request(path: "third");

// takeRequest 是 FIFO
// 应该将 takeRequest() 赋值给一个变量,以便验证多个属性
expect(server.takeRequest().headers['x-header'], "nosniff");
expect(server.takeRequest().method, "GET");
expect(server.takeRequest().uri.path, "/third");

使用调度器进行细粒度路由

如果您需要比队列更多的控制,可以设置调度器并设置逻辑。

var dispatcher = (HttpRequest request) {
  if (request.uri.path == "/users") {
    return new MockResponse()
      ..httpCode = 200
      ..body = "working";
  } else if (request.uri.path == "/users/1") {
    return new MockResponse()..httpCode = 201;
  }

  return new MockResponse()..httpCode = 404;
};

server.dispatcher = dispatcher;

HttpClientResponse response = request(path: "unknown");
expect(response.statusCode, 404);

response = request(path: "users");
expect(response.statusCode, 200);
expect(read(response), "working");

response = request(path: "users/1");
expect(response.statusCode, 201);

使用 TLS

您可以通过传递 certificate 参数在创建 MockWebServer 实例时启用 TLS。

var chainRes =
    new Resource('package:mock_web_server/certificates/server_chain.pem');
List<int> chain = await chainRes.readAsBytes();

var keyRes =
    new Resource('package:mock_web_server/certificates/server_key.pem');
List<int> key = await keyRes.readAsBytes();

Certificate certificate = new Certificate()
  ..password = "dartdart"
  ..key = key
  ..chain = chain;

MockWebServer _server =
    new MockWebServer(certificate: certificate);

如果客户端验证证书,则需要使用适当的 SecurityContext,例如使用包含的 trusted_certs.pem

var certRes =
    new Resource('package:mock_web_server/certificates/trusted_certs.pem');
List<int> cert = await certRes.readAsBytes();

SecurityContext clientContext = new SecurityContext()
   ..setTrustedCertificatesBytes(cert);
    
var client = new HttpClient(context: clientContext);

使用 IPv6

如果您想使用 IPv6,可以在构造函数中传递 addressType: InternetAddressType.IP_V6 来让服务器使用它。

MockWebServer _server =
        new MockWebServer(port: 8030, addressType: InternetAddressType.IP_V6);

设置默认响应

在某些情况下,如果没有队列中的内容且没有调度器,您可能希望服务器返回一个默认响应(如 404)而不是抛出异常。

_server.defaultResponse = MockResponse()..httpCode = 404;

停止服务器

在测试的 tearDown 阶段,应该停止服务器。使用 server.shutdown() 即可。

tearDown(() {
  _server.shutdown();
});

完整示例代码

/*
 * Copyright (C) 2017 Miguel Castiblanco
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import 'package:mock_web_server/mock_web_server.dart';
import 'package:test/test.dart';
import 'dart:io';
import 'dart:async';
import 'dart:convert';
import 'package:resource/resource.dart' show Resource;

MockWebServer _server;

void main() {
  setUp(() {
    _server = new MockWebServer();
    _server.start();
  });

  tearDown(() {
    _server.shutdown();
  });

  test("Set response code", () async {
    _server.enqueue(httpCode: 401);
    HttpClientResponse response = await _get("");
    expect(response.statusCode, 401);
  });

  test("Set body", () async {
    _server.enqueue(body: "something");
    HttpClientResponse response = await _get("");
    expect(await _read(response), "something");
  });

  test("Set headers", () async {
    Map<String, String> headers = new Map();
    headers["X-Server"] = "MockDart";

    _server.enqueue(body: "Created", httpCode: 201, headers: headers);
    HttpClientResponse response = await _get("");
    expect(response.statusCode, 201);
    expect(response.headers.value("X-Server"), "MockDart");
    expect(await _read(response), "Created");
  });

  test("Set body and response code", () async {
    _server.enqueue(body: "Created", httpCode: 201);
    HttpClientResponse response = await _get("");
    expect(response.statusCode, 201);
    expect(await _read(response), "Created");
  });

  test("Set body, response code, and headers", () async {
    Map<String, String> headers = new Map();
    headers["X-Server"] = "MockDart";

    _server.enqueue(body: "Created", httpCode: 201, headers: headers);
    HttpClientResponse response = await _get("");
    expect(response.statusCode, 201);
    expect(response.headers.value("X-Server"), "MockDart");
    expect(await _read(response), "Created");
  });

  test("Queue", () async {
    _server.enqueue(body: "hello");
    _server.enqueue(body: "world");
    HttpClientResponse response = await _get("");
    expect(await _read(response), "hello");

    response = await _get("");
    expect(await _read(response), "world");
  });

  test("Take requests & request count", () async {
    _server.enqueue(body: "a");
    _server.enqueue(body: "b");
    _server.enqueue(body: "c");
    await _get("first");
    await _get("second");
    await _get("third");

    expect(_server.takeRequest().uri.path, "/first");
    expect(_server.takeRequest().uri.path, "/second");
    expect(_server.takeRequest().uri.path, "/third");
    expect(_server.requestCount, 3);
  });

  test("Request count", () async {
    _server.enqueue(httpCode: HttpStatus.unauthorized);

    await _get("first");

    expect(_server.takeRequest().uri.path, "/first");
    expect(_server.requestCount, 1);
  });

  test("Dispatcher", () async {
    var dispatcher = (HttpRequest request) async {
      if (request.uri.path == "/users") {
        return new MockResponse()
          ..httpCode = 200
          ..body = "working";
      } else if (request.uri.path == "/users/1") {
        return new MockResponse()..httpCode = 201;
      } else if (request.uri.path == "/delay") {
        return new MockResponse()
          ..httpCode = 200
          ..delay = new Duration(milliseconds: 1500);
      }

      return new MockResponse()..httpCode = 404;
    };

    _server.dispatcher = dispatcher;

    HttpClientResponse response = await _get("unknown");
    expect(response.statusCode, 404);

    response = await _get("users");
    expect(response.statusCode, 200);
    expect(await _read(response), "working");

    response = await _get("users/1");
    expect(response.statusCode, 201);

    Stopwatch stopwatch = new Stopwatch()..start();
    response = await _get("delay");
    stopwatch.stop();
    expect(stopwatch.elapsed.inMilliseconds,
        greaterThanOrEqualTo(new Duration(milliseconds: 1500).inMilliseconds));
    expect(response.statusCode, 200);
  });

  test("Enqueue MockResponse", () async {
    Map<String, String> headers = new Map();
    headers["X-Server"] = "MockDart";

    var mockResponse = new MockResponse()
      ..httpCode = 201
      ..body = "Created"
      ..headers = headers;

    _server.enqueueResponse(mockResponse);
    HttpClientResponse response = await _get("");
    expect(response.statusCode, 201);
    expect(response.headers.value("X-Server"), "MockDart");
    expect(await _read(response), "Created");
  });

  test("Delay", () async {
    _server.enqueue(delay: new Duration(seconds: 2), httpCode: 201);
    Stopwatch stopwatch = new Stopwatch()..start();
    HttpClientResponse response = await _get("");

    stopwatch.stop();
    expect(stopwatch.elapsed.inMilliseconds,
        greaterThanOrEqualTo(new Duration(seconds: 2).inMilliseconds));
    expect(response.statusCode, 201);
  });

  test('Parallel delay', () async {
    String body70 = "70 milliseconds";
    String body40 = "40 milliseconds";
    String body20 = "20 milliseconds";
    _server.enqueue(delay: new Duration(milliseconds: 40), body: body40);
    _server.enqueue(delay: new Duration(milliseconds: 70), body: body70);
    _server.enqueue(delay: new Duration(milliseconds: 20), body: body20);

    Completer completer = new Completer();
    List<String> responses = new List();

    _get("").then((res) async {
      // 40 milliseconds
      String result = await _read(res);
      responses.add(result);
    });

    _get("").then((res) async {
      // 70 milliseconds
      String result = await _read(res);
      responses.add(result);

      // complete on the longer operation
      completer.complete();
    });

    _get("").then((res) async {
      // 20 milliseconds
      String result = await _read(res);
      responses.add(result);
    });

    await completer.future;

    // validate that the responses happened in order 20, 40, 70
    expect(responses[0], body20);
    expect(responses[1], body40);
    expect(responses[2], body70);
  });

  test("Request specific port IPv4", () async {
    MockWebServer _server = new MockWebServer(port: 8029);
    await _server.start();

    RegExp url = new RegExp(r'(?:http[s]?:\/\/(?:127\.0\.0\.1):8029\/)');
    RegExp host = new RegExp(r'(?:127\.0\.0\.1)');

    expect(url.hasMatch(_server.url), true);
    expect(host.hasMatch(_server.host), true);
    expect(_server.port, 8029);

    _server.shutdown();
  });

  test("Request specific port IPv6", () async {
    MockWebServer _server =
        new MockWebServer(port: 8030, addressType: InternetAddressType.IPv6);
    await _server.start();

    RegExp url = new RegExp(r'(?:http[s]?:\/\/(?:::1):8030\/)');
    RegExp host = new RegExp(r'(?:::1)');

    expect(url.hasMatch(_server.url), true);
    expect(host.hasMatch(_server.host), true);
    expect(_server.port, 8030);

    _server.shutdown();
  });

  test("TLS info", () async {
    var chainRes =
        new Resource('package:mock_web_server/certificates/server_chain.pem');
    List<int> chain = await chainRes.readAsBytes();

    var keyRes =
        new Resource('package:mock_web_server/certificates/server_key.pem');
    List<int> key = await keyRes.readAsBytes();

    Certificate certificate = new Certificate()
      ..password = "dartdart"
      ..key = key
      ..chain = chain;

    MockWebServer _server =
        new MockWebServer(port: 8029, certificate: certificate);
    await _server.start();

    RegExp url = new RegExp(r'(?:https:\/\/(?:127\.0\.0\.1):8029\/)');
    RegExp host = new RegExp(r'(?:127\.0\.0\.1)');

    expect(url.hasMatch(_server.url), true);
    expect(host.hasMatch(_server.host), true);
    expect(_server.port, 8029);

    _server.shutdown();
  });

  test("TLS cert", () async {
    String body = "S03E08 You Are Not Safe";

    var chainRes =
        new Resource('package:mock_web_server/certificates/server_chain.pem');
    List<int> chain = await chainRes.readAsBytes();

    var keyRes =
        new Resource('package:mock_web_server/certificates/server_key.pem');
    List<int> key = await keyRes.readAsBytes();

    Certificate certificate = new Certificate()
      ..password = "dartdart"
      ..key = key
      ..chain = chain;

    MockWebServer _server =
        new MockWebServer(port: 8029, certificate: certificate);
    await _server.start();
    _server.enqueue(body: body);

    var certRes =
        new Resource('package:mock_web_server/certificates/trusted_certs.pem');
    List<int> cert = await certRes.readAsBytes();

    // Calling without the proper security context
    var clientErr = new HttpClient();

    expect(clientErr.getUrl(Uri.parse(_server.url)),
        throwsA(new TypeMatcher<HandshakeException>()));

    // Testing with security context
    SecurityContext clientContext = new SecurityContext()
      ..setTrustedCertificatesBytes(cert);

    var client = new HttpClient(context: clientContext);
    var request = await client.getUrl(Uri.parse(_server.url));
    String response = await _read(await request.close());

    expect(response, body);

    _server.shutdown();
  });

  test("Check take request", () async {
    _server.enqueue();

    HttpClient client = new HttpClient();
    HttpClientRequest request =
        await client.post(_server.host, _server.port, "test");
    request.headers.add("x-header", "nosniff");
    request.write("sample body");

    await request.close();
    StoredRequest storedRequest = _server.takeRequest();

    expect(storedRequest.method, "POST");
    expect(storedRequest.body, "sample body");
    expect(storedRequest.uri.path, "/test");
    expect(storedRequest.headers['x-header'], "nosniff");
  });

  test("default response", () async {
    _server.defaultResponse = MockResponse()..httpCode = 404;

    var response = await _get("");
    expect(response.statusCode, 404);
  });
}

_get(String path) async {
  HttpClient client = new HttpClient();
  HttpClientRequest request =
      await client.get(_server.host, _server.port, path);
  return await request.close();
}

Future<String> _read(HttpClientResponse response) async {
  StringBuffer body = new StringBuffer();
  Completer<String> completer = new Completer();

  response.transform(utf8.decoder).listen((data) {
    body.write(data);
  }, onDone: () {
    completer.complete(body.toString());
  });

  await completer.future;
  return body.toString();
}

更多关于Flutter本地模拟服务器插件mock_web_server的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter本地模拟服务器插件mock_web_server的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


mock_web_server 是一个用于在 Flutter 中模拟 HTTP 服务器的插件。它通常用于在本地开发环境中模拟 API 请求,以便在没有实际后端服务器的情况下进行测试和开发。

安装 mock_web_server

首先,你需要在 pubspec.yaml 文件中添加 mock_web_server 依赖:

dev_dependencies:
  mock_web_server: ^5.0.0

然后运行 flutter pub get 来安装依赖。

使用 mock_web_server

以下是一个简单的示例,展示如何使用 mock_web_server 来模拟一个 HTTP 服务器并处理请求。

import 'package:flutter_test/flutter_test.dart';
import 'package:mock_web_server/mock_web_server.dart';

void main() {
  late MockWebServer server;

  setUp(() {
    server = MockWebServer();
  });

  tearDown(() async {
    await server.shutdown();
  });

  test('Test mock web server', () async {
    // 启动服务器
    await server.start();

    // 模拟一个 GET 请求的响应
    server.enqueue(
      httpCode: 200,
      body: 'Hello, World!',
    );

    // 发送请求到模拟服务器
    final response = await http.get(Uri.parse(server.url));

    // 验证响应
    expect(response.statusCode, 200);
    expect(response.body, 'Hello, World!');
  });

  test('Test POST request', () async {
    // 启动服务器
    await server.start();

    // 模拟一个 POST 请求的响应
    server.enqueue(
      httpCode: 201,
      body: '{"message": "Created"}',
      headers: {'Content-Type': 'application/json'},
    );

    // 发送 POST 请求到模拟服务器
    final response = await http.post(
      Uri.parse(server.url),
      body: '{"data": "value"}',
      headers: {'Content-Type': 'application/json'},
    );

    // 验证响应
    expect(response.statusCode, 201);
    expect(response.body, '{"message": "Created"}');
  });
}

主要功能

  1. 启动和停止服务器:

    • server.start(): 启动模拟服务器。
    • server.shutdown(): 停止模拟服务器。
  2. 模拟响应:

    • server.enqueue(): 用于模拟服务器的响应。你可以指定 HTTP 状态码、响应体和响应头。
  3. 获取服务器 URL:

    • server.url: 获取模拟服务器的 URL,用于发送请求。
  4. 验证请求:

    • server.takeRequest(): 获取并验证发送到服务器的请求。你可以检查请求的方法、路径、头和体。

示例:验证请求

test('Verify request details', () async {
  await server.start();

  server.enqueue(httpCode: 200, body: 'OK');

  await http.get(Uri.parse('${server.url}/test'));

  final request = server.takeRequest();
  expect(request.method, 'GET');
  expect(request.uri.path, '/test');
});
回到顶部