内容简介:I releasedThe nameYou can spin up as many mock HTTP servers as you need to mock all the 3rd party APIs that your application interacts with.
TL;DR
I released wiremock
, a new crate that provides HTTP mocking to test Rust applications.
1use wiremock::{MockServer, Mock, ResponseTemplate}; 2use wiremock::matchers::{method, path}; 3 4#[async_std::main] 5async fn main() { 6 // Start a background HTTP server on a random local port 7 let mock_server = MockServer::start().await; 8 9 // Arrange the behaviour of the MockServer adding a Mock: 10 // when it receives a GET request on '/hello' it will respond with a 200. 11 Mock::given(method("GET")) 12 .and(path("/hello")) 13 .respond_with(ResponseTemplate::new(200)) 14 // Mounting the mock on the mock server - it's now effective! 15 .mount(&mock_server) 16 .await; 17 18 // If we probe the MockServer using any HTTP client it behaves as expected. 19 let status = surf::get(format!("{}/hello", &mock_server.uri())) 20 .await 21 .unwrap() 22 .status(); 23 assert_eq!(status.as_u16(), 200); 24 25 // If the request doesn't match any `Mock` mounted on our `MockServer` 26 // a 404 is returned. 27 let status = surf::get(format!("{}/missing", &mock_server.uri())) 28 .await 29 .unwrap() 30 .status(); 31 assert_eq!(status.as_u16(), 404); 32} 33
The name wiremock
is a reference to WireMock.Net
, a .NET port of the original Wiremock
from Java.
You can spin up as many mock HTTP servers as you need to mock all the 3rd party APIs that your application interacts with.
Mock HTTP servers are fully isolated: tests can be run in parallel, with no interference. Each server is shut down when it goes out of scope (e.g. end of test execution).
wiremock
provides out-of-the-box a set of matching strategies, but more can be defined to suit your testing needs via the Match trait.
wiremock
is asynchronous: it is compatible (and tested) against both async_std
and tokio
as runtimes.
Why did we need another HTTP mocking crate?
The backstory
I spent part of last week working on a project that might lead to the first Rust REST API in production at my current company.
As it happens when you have been advocating for a technology, it was not enough to get the job done.
I set out to write the best possible sample : a showcase of what an idiomatic Rust API should look like when it comes to logging, error handling, metrics, testing, domain modelling, etc. - a code base my colleagues could use as a reference if they were to choose Rust for a future project.
All in all things went pretty smoothly - everything I needed was available as a somewhat polished crate, already provided by the Rust community.
Everything but one key piece: HTTP mocking.
The problem
It is extremely valuable to have a set of tests in your suite that interact with your service via its public API, as a user, without any knowledge of its inner workings.
These API tests can be used to verify acceptance criteria for user stories and prevent regressions.
They are as well generally easier to maintain: they are fairly decoupled from the implementation details, with a focus on the behaviour that is visible to a user of the API.
The caveat: no API is an island, especially in a microservice architecture - your service is likely to interact with the public APIs of a number of external dependencies.
When performing API tests you generally do not want to spin up those external dependencies in your continuous integration pipeline - if your microservice architecture is intricate enough, you might end spinning up tens of services to test a tiny piece of functionality in your API under test. The setup alone could take a significant amount of time - best to run those tests in a test cluster, as a final pre-deployment check.
Furthermore, you might simply not be able to spin up some of those dependencies (e.g. 3rd party SaaS services).
For our run-it-on-every-commit CI pipeline, we can get away with most of the value without putting in a crazy amount of effort: we can use HTTP mocking .
We spin up an HTTP server in the background for each of our tests, define a set of request-response scenarios ( return response A if you receive request B ) and then configure our application to use the mock server in lieu of the real service.
The available options
Two existing crates might have provided what I needed: mockito
and httpmock
.
Unfortunately, they both suffer by a set of limitations which I didn’t want to live with:
- You can only run a single mock server, hence you can only mock a single external API;
- Tests must be run sequentially;
- No way to define custom request matchers to extend the functionality provided out of the box by the crate.
Easter, 4 days-long break - I set out to write a new HTTP mocking crate, wiremock
.
Wiremock
wiremock
provides fully-isolated MockServer
s : start
takes care of finding a random port available on your local machine which is assigned to the new server.
You can use one instance of MockServer
for each test and for each 3rd party API you need to mock - each server is shut down when it goes out of scope (e.g. end of test execution).
1use wiremock::{MockServer, Mock, ResponseTemplate}; 2use wiremock::matchers::method; 3 4#[async_std::main] 5async fn main() { 6 // Arrange 7 let mock_server_one = MockServer::start().await; 8 let mock_server_two = MockServer::start().await; 9 10 assert!(mock_server_one.address() != mock_server_two.address()); 11 12 let mock = Mock::given(method("GET")).respond_with(ResponseTemplate::new(200)); 13 // Registering the mock with the first mock server - it's now effective! 14 // But it *won't* be used by the second mock server! 15 mock_server_one.register(mock).await; 16 17 // It matches our mock 18 let status = surf::get(&mock_server_one.uri()) 19 .await 20 .unwrap() 21 .status(); 22 assert_eq!(status.as_u16(), 200); 23 24 // This would have matched our mock, but we haven't registered it 25 // for `mock_server_two`! 26 // Hence it returns a 404, the default response when 27 // no mocks matched on the mock server. 28 let status = surf::get(&mock_server_two.uri()) 29 .await 30 .unwrap() 31 .status(); 32 assert_eq!(status.as_u16(), 404); 33} 34
wiremock
provides a set of matching strategies out of the box - see the matchers
module for a complete list.
You can also define your own matchers using the Match
trait, as well as using Fn
closures.
How does it work?
Each instance of MockServer
is, under the hood, a pair of actors running on Bastion
:
- a server actor, listening on an
async_std::net::TcpListener
; - a mock actor, in charge of registering and managing mocks.
When a request hits a MockServer
:
- the server actor tries to parse it from a
async_std::net::TcpStream
usingasync-h1
; - the parsed request is passed as a message to the mock actor;
- the mock actor checks all mocks against it, one by one - if one matches, the associated response is returned; if nothing matches a 404 is returned;
- the server actor responds to the caller.
The two actors are killed when MockServer
is dropped.
Going forward
Well, testing for the upcoming Rust API is surely going to be shiny!
For the overall Rust community, I foresee two main development directions when it comes to wiremock
:
- Spying is the biggest piece of functionality
wiremock
is currently missing: being able to assert if a mock matched, or how many times it did.
Quite useful when you want to verify that certain side-effects have been triggered by your application. - More matching strategies for common use cases will make their way into
wiremock
as time goes forward.
以上所述就是小编给大家介绍的《Wiremock: async HTTP mocking to test Rust applications》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机程序设计艺术(第1卷)
[美] 唐纳德·E. 克努特 / 苏运霖 / 国防工业出版社 / 2002-9 / 98.00元
7卷本《计算机程序设计艺术》的第1卷以基本的程序设计概念和技术开始,然后专注于信息结构——计算机内部信息的表示、数据元素之间的结构关系以及如何有效地处理它们,给出了对于模拟、数值方法、符号计算、软件和系统设计的初等应用。书中附有大量习题和答案,标明了难易程序及数学概念的使用。 此新版本增加了几十项简单且重要的算法和技术,并对有关数学预备知识作了大量修改以适应现时研究的趋势。一起来看看 《计算机程序设计艺术(第1卷)》 这本书的介绍吧!