1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
//! The `pact_consumer` crate provides tools for writing consumer [Pact //! tests][pact]. It implements the [V3 Pact specification][spec]. You can also //! use it as a simple HTTP mocking library for Rust. //! //! [pact]: https://docs.pact.io/ [spec]: //! https://github.com/pact-foundation/pact-specification //! //! ## What is Pact? //! //! [Pact][pact] is a [cross-language standard][spec] for testing the //! communication between the consumer of a REST API, and the code that provides //! that API. Test cases are written from the consumer's perspective, and they //! can then be exported testing the provider. //! //! The big advantages of Pact are: //! //! 1. The mocks you write to test the client can also be reused to verify that //! the server would actually respond the way the client expects. This gives //! the end-to-end assurance of integration tests (well, almost), but with //! the speed and convenience of unit tests. //! 2. Pact has been implemented in many popular languages, so you can test //! clients and servers in multiple languages. //! //! Whenever possible, we try to use vocabulary similar to the Ruby or //! JavaScript API for basic concepts, and we try to provide the same behavior. //! But we offer many handy builder methods to make tests cleaner. //! //! ## How to use it //! //! To use this crate, add it to your `[dev-dependencies]` in your `Cargo.toml`: //! //! ```toml //! [dev-dependencies] //! pact_consumer = "0.4.0" //! ``` //! //! Once this is done, you can then write the following inside a function marked //! with `#[test]`: //! //! ``` //! # fn main() { //! use pact_consumer::prelude::*; //! //! // Define the Pact for the test, specify the names of the consuming //! // application and the provider application. //! let pact = PactBuilder::new("Consumer", "Alice Service") //! // Start a new interaction. We can add as many interactions as we want. //! .interaction("a retrieve Mallory request", |i| { //! // Defines a provider state. It is optional. //! i.given("there is some good mallory"); //! // Define the request, a GET (default) request to '/mallory'. //! i.request.path("/mallory"); //! // Define the response we want returned. We assume a 200 OK //! // response by default. //! i.response //! .content_type("text/plain") //! .body("That is some good Mallory."); //! }) //! .build(); //! # } //! ``` //! //! You can than use an HTTP client like `reqwest` to make requests against your //! server. //! //! ```rust,no_run //! # // This is marked `no_run` because of the issues described in //! # // https://github.com/rust-lang/cargo/issues/4567. An executable //! # // version is checked in tests/tests.rs. //! # use pact_matching::models::Pact; //! # use std::io::Read; //! # fn main() { //! # use pact_consumer::prelude::*; //! # let pact: Pact = unimplemented!(); //! // Start the mock server running. //! let alice_service = pact.start_mock_server(); //! //! // You would use your actual client code here. //! let mallory_url = alice_service.path("/mallory"); //! let mut response = reqwest::get(mallory_url).expect("could not fetch URL"); //! let mut body = String::new(); //! response.read_to_string(&mut body).expect("could not read response body"); //! assert_eq!(body, "That is some good Mallory."); //! //! // When `alice_service` goes out of scope, your pact will be validated, //! // and the test will fail if the mock server didn't receive matching //! // requests. //! # } //! ``` //! //! ## Matching using patterns //! //! You can also use patterns like `like!`, `each_like!` or `term!` to allow //! more general matches, and you can build complex patterns using the //! `json_pattern!` macro: //! //! ``` //! # fn main() { //! use pact_consumer::prelude::*; //! use pact_consumer::*; //! //! PactBuilder::new("quotes client", "quotes service") //! .interaction("add a new quote to the database", |i| { //! i.request //! .post() //! .path("/quotes") //! .json_utf8() //! .json_body(json_pattern!({ //! // Allow the client to send any string as a quote. //! // When testing the server, use "Eureka!". //! "quote": like!("Eureka!"), //! // Allow the client to send any string as an author. //! // When testing the server, use "Archimedes". //! "by": like!("Archimedes"), //! // Allow the client to send an array of strings. //! // When testing the server, send a single-item array //! // containing the string "greek". //! "tags": each_like!("greek"), //! })); //! //! i.response //! .created() //! // Return a location of "/quotes/12" to the client. When //! // testing the server, allow it to return any numeric ID. //! .header("Location", term!("^/quotes/[0-9]+$", "/quotes/12")); //! }); //! # } //! ``` //! //! The key insight here is this "pact" can be used to test both the client and //! the server: //! //! - When testing the **client**, we allow the request to be anything which //! matches the patterns—so `"quote"` can be any string, not just `"Eureka!"`. //! But we respond with the specified values, such as `"/quotes/12"`. //! - When testing the **server**, we send the specified values, such as //! `"Eureka!"`. But we allow the server to respond with anything matching the //! regular expression `^/quotes/[0-9]+$`, because we don't know what database //! ID it will use. //! //! Also, when testing the server, we may need to set up particular database //! fixtures. This can be done using the string passed to `given` in the //! examples above. //! //! ## Testing using domain objects //! //! Normally, it's best to generate your JSON using your actual domain objects. //! This is easier, and it reduces duplication in your code. //! // This fails to link with Rust beta 1.27.0 //! ```ignore //! use pact_consumer::prelude::*; //! //! /// Our application's domain object representing a user. //! #[derive(Deserialize, Serialize)] //! struct User { //! /// All users have this field. //! name: String, //! //! /// The server may omit this field when sending JSON, or it may send it //! /// as `null`. //! comment: Option<String>, //! } //! //! # fn main() { //! // Create our example user using our normal application objects. //! let example = User { //! name: "J. Smith".to_owned(), //! comment: None, //! }; //! //! PactBuilder::new("consumer", "provider") //! .interaction("get all users", |i| { //! i.given("a list of users in the database"); //! i.request.path("/users"); //! i.response //! .json_utf8() //! .json_body(each_like!( //! // Here, `strip_null_fields` will remove `comment` from //! // the generated JSON, allowing our pattern to match //! // missing comments, null comments, and comments with //! // strings. //! strip_null_fields(json!(example)), //! )); //! }) //! .build(); //! # } //! ``` //! //! For more advice on writing good pacts, see [Best Practices][]. //! //! [Best Practices]: https://docs.pact.io/best_practices/consumer.html #![warn(missing_docs)] // Child modules which define macros (must be first because macros are resolved) // in source inclusion order). #[macro_use] pub mod patterns; #[cfg(test)] #[macro_use] mod test_support; // Other child modules. pub mod builders; pub mod mock_server; pub mod util; /// A "prelude" or a default list of import types to include. This includes /// the basic DSL, but it avoids including rarely-used types. /// /// ``` /// use pact_consumer::prelude::*; /// ``` pub mod prelude { pub use crate::builders::{HttpPartBuilder, PactBuilder}; pub use crate::patterns::{Pattern, JsonPattern, StringPattern}; pub use crate::patterns::{EachLike, Like, Term}; pub use crate::mock_server::{StartMockServer, ValidatingMockServer}; pub use crate::util::strip_null_fields; }