├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── Readme.md ├── src ├── acquire.rs ├── data.rs ├── expect.rs ├── generate │ ├── behaviour.rs │ ├── binding_implementer.rs │ ├── mock_struct_implementer.rs │ ├── mod.rs │ ├── trait_implementer.rs │ └── type_param_mapper.rs ├── given.rs ├── lib.rs └── new_mock.rs └── tests ├── expect_different_interactions.rs ├── expect_method_without_args.rs ├── expect_void_method.rs ├── galvanic_assert_integration.rs ├── given_block_missing_for_method.rs ├── given_different_repeats.rs ├── given_different_returns.rs ├── given_method_with_multiple_args.rs ├── given_method_with_single_arg.rs ├── given_method_without_args.rs ├── given_void_method_without_args.rs ├── mock_empty_trait.rs ├── mock_generic_trait.rs ├── mock_generic_trait_method.rs ├── mock_referred_trait.rs ├── mock_trait_and_apply_attributes.rs ├── mock_trait_in_submodules.rs ├── mock_with_explicit_type_name.rs ├── use_mocks_on_module.rs └── verify_on_drop.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "galvanic-mock" 3 | version = "0.1.3" 4 | authors = ["Christopher Bacher "] 5 | 6 | description = """A behaviour-driven mocking framework for generic traits. 7 | 8 | Create mocks for (multiple) traits in a behaviour-driven development mocking framework. 9 | Define the behaviour of mocks and expected method calls using argument patterns. 10 | Supports mocking of generic traits and generic methods. 11 | Requires: nightly""" 12 | 13 | homepage = "https://github.com/mindsbackyard/galvanic-mock" 14 | repository = "https://github.com/mindsbackyard/galvanic-mock" 15 | documentation = "https://github.com/mindsbackyard/galvanic-mock" 16 | 17 | readme = "Readme.md" 18 | 19 | license = "Apache-2.0" 20 | 21 | keywords = ["test", "mocking", "TDD", "BDD"] 22 | categories = ["development-tools::testing"] 23 | 24 | [dependencies] 25 | synom = "0.11" 26 | quote = "0.3" 27 | lazy_static = "0.2" 28 | galvanic-assert = { version = "0.8", optional = true} 29 | 30 | [dependencies.syn] 31 | version = "0.11" 32 | features = ["full", "parsing"] 33 | 34 | [dev-dependencies] 35 | galvanic-assert = "0.8" 36 | 37 | [lib] 38 | proc-macro = true 39 | 40 | [features] 41 | galvanic_assert_integration = ["galvanic-assert"] 42 | 43 | [badges] 44 | travis-ci = { repository = "mindsbackyard/galvanic-mock" } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Galvanic-mock: behaviour-driven mocking for generic traits 2 | [![Build Status](https://travis-ci.org/mindsbackyard/galvanic-mock.svg?branch=master)](https://travis-ci.org/mindsbackyard/galvanic-mock) 3 | [![Crates.io](https://img.shields.io/crates/v/galvanic-mock.svg)](https://crates.io/crates/galvanic-mock) 4 | 5 | This crate provides procedural macros (`#[mockable]`, `#[use_mocks]`) for mocking the behaviour of traits. 6 | 7 | * define given **behaviours** for mock objects based on **patterns** 8 | * state **expectations** for interactions with mocks 9 | * mock **multiple** traits at once 10 | * mock **generic traits** and **traits with associated types** 11 | * mock **generic trait methods** 12 | * apply **#[derive(..)]** and other attributes to your mocks 13 | * **[galvanic-assert](https://www.github.com/mindsbackyard/galvanic-assert)** matchers like `eq`, `lt`, ... can be used in behaviours 14 | * integrate with **[galvanic-test](https://www.github.com/mindsbackyard/galvanic-test)** and **[galvanic-assert](https://www.github.com/mindsbackyard/galvanic-assert)** 15 | * be used with your favourite test framework 16 | 17 | The crate is part of **galvanic**---a complete test framework for **Rust**. 18 | The framework is shipped in three parts, so you can choose to use only the parts you need. 19 | 20 | ## A short introduction to galvanic-mock 21 | 22 | In a well designed software project with loose coupling and dependency injection, 23 | a mock eases the development of software tests. It imitates the behaviour of a *real* object, 24 | i.e., an object present in the production code, to decouple a tested software component from the rest of the system. 25 | 26 | **Galvanic-mock** is a behaviour-driven mocking library for traits in Rust. 27 | It allows the user to create a mock object for one or multiple traits emulating their behaviour according to **given** patterns of interaction. 28 | A pattern for a trait's method consists of a boolean matcher for its argument (either for each argument or for all at once), a constant or function calculating the return value, and a the number of repetitions for which the pattern is valid. 29 | 30 | ```rust 31 | // this crate requires a nightly version of rust 32 | #![feature(proc_macro)] 33 | extern crate galvanic_mock; 34 | use galvanic_mock::{mockable, use_mocks}; 35 | 36 | #[mockable] 37 | trait MyTrait { 38 | fn foo(&self, x: i32, y: i32) -> i32; 39 | } 40 | 41 | #[test] 42 | #[use_mocks] 43 | fn simple_usage_of_mocks() { 44 | // create a new object implementing `MyTrait` 45 | let mock = new_mock!(MyTrait); 46 | let some_calculation = 1 + 2*3; 47 | 48 | // define behaviours how your mocks should react given some input 49 | given! { 50 | // make val available to your behaviours (must implement `Clone`), the type is **not** optional! 51 | bind val: i32 = some_calculation; 52 | 53 | // define input matchers per argument and return a constant value whenever it matches 54 | ::foo(|&x| x < 7, |&y| y % 2 == 0) then_return 12 always; 55 | // or define a single input matcher for all arguments and return the result of a function 56 | ::foo |&(x, y)| x < y then_return_from |&(x,y)| y - x always; 57 | // with the `bound` variable you can access variable declared with `bind VAR: TYPE = VALUE;` 58 | ::foo(|_| true) then_return_from |&(x,_)| x*bound.val always; 59 | } 60 | 61 | // only matches the last behaviour 62 | assert_eq!(mock.foo(12, 4), 84); 63 | // would match the first and the second behaviour, but the first matching behaviour is always used 64 | assert_eq!(mock.foo(3, 4), 12); 65 | // matches the second behaviour 66 | assert_eq!(mock.foo(12, 14), 2); 67 | } 68 | ``` 69 | 70 | Besides emulating the behaviour of an object it is also possible to state expectations about the interactions with object. 71 | Patterns for **expected** behaviours work similar to pattterns for given behaviours. 72 | The example below illustrates these concepts. 73 | 74 | ```rust 75 | #![feature(proc_macro)] 76 | extern crate galvanic_mock; 77 | use galvanic_mock::{mockable, use_mocks}; 78 | 79 | // matchers from galvanic_assert can be used as argument matchers 80 | extern crate galvanic_assert; 81 | use galvanic_assert::matchers::{gt, leq, any_value}; 82 | 83 | #[mockable] 84 | trait MyTrait { 85 | fn foo(&self, x: i32, y: i32) -> i32; 86 | } 87 | 88 | #[mockable] 89 | trait MyOtherTrait { 90 | fn bar(&self, x: T) -> T; 91 | } 92 | 93 | #[test] 94 | #[use_mocks] 95 | fn simple_use_of_mocks() { 96 | // to mock multiple traits just separate them with a colon 97 | // specify all types for generic traits as you would specify a type 98 | let mock = new_mock!(MyTrait, MyOtherTrait); 99 | 100 | // expectations are matched top-down, but once the specified match count is reached it won't match again 101 | given! { 102 | // instead of repeating the trait over and over, you can open a block 103 | ::{ 104 | // this behaviour will match only twice 105 | foo any_value() then_return_from |_| 7 times 2; 106 | foo(gt(12), any_value()) then_return 2 always; 107 | }; 108 | // for generic traits all generic types and associated types need to be given 109 | >::bar(|x| x == "hugo") then_return "got hugo".to_string() always; 110 | } 111 | 112 | // expectations are matched top-down, but will never be exhausted 113 | expect_interactions! { 114 | // `times` expects an exact number of matching interactions 115 | >::bar any_value() times 1; 116 | // besides `times`, also `at_least`, `at_most`, `between`, and `never` are supported 117 | // all limits are inclusive 118 | ::foo(any_value(), leq(2)) between 2,5; 119 | } 120 | 121 | assert_eq!(mock.foo(15, 1), 7); 122 | assert_eq!(mock.bar("hugo".to_string()), "got hugo".to_string()); 123 | assert_eq!(mock.foo(15, 2), 7); 124 | assert_eq!(mock.foo(15, 5), 2); 125 | 126 | // the expected interactions are verified when the mock is dropped or when `mock.verify()` is called 127 | } 128 | ``` 129 | 130 | 131 | ## Documentation 132 | 133 | Before reading the documentation make sure to read the examples in the introduction as the documentation will use them as a basis for explanation. 134 | 135 | To use the mocking library make sure that you use a **nightly** version of Rust as the crate requires the `proc_macro` feature. 136 | Add the dependency to your `Cargo.toml` preferably as a dev dependency. 137 | ```toml 138 | [dev-dependencies] 139 | galvanic-mock = "*" # galvanic uses `semver` versioning 140 | ``` 141 | 142 | At the root of your crate (either `main.rs` or `lib.rs`) add the following to activate the required features and to import the macros. 143 | ```rust 144 | #![feature(proc_macro)] 145 | extern crate galvanic_mock; 146 | // The use statement should be placed where the #[mocakable] and #[use_mocks] attributes 147 | // are actually used, or reimported. 148 | use galvanic_mock::{mockable, use_mocks}; 149 | ``` 150 | 151 | If we want to use `galvanic-assert` matchers in mocks then we have to enable the `galvanic_assert_integration` feature as follows. 152 | ```toml 153 | [dev-dependencies] 154 | galvanic-mock = { version = "*", features = ["galvanic_assert_integration"] } 155 | galvanic-assert = "*" # galvanic-assert uses semver versioning too. To find the version required by `galvanic-mock` check version of the optional dependency in the manifest `Cargo.toml`. 156 | ``` 157 | If the integration feature is enabled, `extern crate galvanic_assert` has to be specified along with `extern crate galvanic_mock` or the library will fail to compile (even if no `galvanic_assert` matchers are used). 158 | 159 | ### Defining mockable traits with `#[mockable]` 160 | 161 | Before a trait can be mocked, you have to tell the mocking framework about its name, generics, associated types, and methods. 162 | If the trait is part of your own crate you just apply the `#[mockable]` attribute to the trait definition. 163 | ```Rust 164 | #[mockable] 165 | trait MyTrait { 166 | fn foo(&self, x: i32, y: i32) -> i32; 167 | } 168 | ``` 169 | This registers `MyTrait` as mockable. 170 | Further it assumes that `MyTrait` is defined at the top-level of a crate or that it is always imported by name when mocked, e.g., with `use crate::module::MyTrait`. 171 | 172 | If the trait is defined in a submodule, its path should be provided to the attribute. 173 | ```Rust 174 | mod sub { 175 | #[mockable(::sub)] 176 | trait MyTrait { 177 | fn foo(&self, x: i32, y: i32) -> i32; 178 | } 179 | } 180 | ``` 181 | 182 | However the trait is annotated, this will be the only way to refer to it later. 183 | There is no name resolution built in, e.g., the above trait must always be used as `::sub::MyTrait`. 184 | The user of the mocked trait is responsible that the trait is visible to the location where the mock is used under the provided path. 185 | It is therefore recommended that *global* paths are used as in the example above. 186 | 187 | #### Mocking *external* traits 188 | 189 | An external trait can be mocked by prefixing the path in the attribute with the `extern` keyword. 190 | The full trait definition must be restated, though its definition will be omitted the macro's expansion. 191 | ```Rust 192 | #[mockable(extern some_crate::sub)] 193 | trait MyTrait { 194 | fn foo(&self, x: i32, y: i32) -> i32; 195 | } 196 | ``` 197 | 198 | #### Fixing issues with the macro expansion order 199 | 200 | As any other macro, `#[mockable]` is subject to the macro expansion order. 201 | Further a mockable trait must be defined before it can be used. 202 | If this is an issue for an internal trait, its definition can be restated similar to *external* traits. 203 | ```Rust 204 | // this occurance of the trait declaration will be removed 205 | #[mockable(intern ::sub)] 206 | trait MyTrait { 207 | fn foo(&self, x: i32, y: i32) -> i32; 208 | } 209 | 210 | // a mock is created somewhere here 211 | ... 212 | 213 | // the true declaration is encountered later 214 | mod sub { 215 | trait MyTrait { 216 | fn foo(&self, x: i32, y: i32) -> i32; 217 | } 218 | } 219 | ``` 220 | 221 | ### Declaring mock usage with `#[use_mocks]` 222 | 223 | Any location (`fn`, `mod`) where mocks should be use must be annotated with `#[use_mocks]`. 224 | ```Rust 225 | #[test] 226 | #[use_mocks] 227 | fn some_test { 228 | ... 229 | } 230 | ``` 231 | 232 | If `#[use_mocks]` is applied to a module then the mock types are shared within all submodules and functions. 233 | ```Rust 234 | #[use_mocks] 235 | mod test_module { 236 | #[test] 237 | fn some_test { 238 | ... 239 | } 240 | 241 | #[test] 242 | fn some_other_test { 243 | ... 244 | } 245 | } 246 | ``` 247 | Though never apply `#[use_mocks]` to an item within some other item which has already a `#[use_mocks]` attribute. 248 | 249 | The following macros can only be used within locations annotated with `#[use_mocks]`. 250 | 251 | ### Creating new mocks with `new_mock!` 252 | 253 | To create a new mock object use the `new_mock!` macro followed by a list of mocked traits. 254 | For generic traits specify all their type arguments and associated types. 255 | The created object satisfies the stated trait bounds and may also be converted into a boxed trait object. 256 | ```Rust 257 | #[use_mocks] 258 | fn some_test { 259 | let mock = new_mock!(MyTrait, MyOtherTrait); 260 | ... 261 | } 262 | ``` 263 | A new mock type will be created for each mock object. 264 | If further attributes should be applied to that type provide them after the type list. 265 | ```Rust 266 | #[use_mocks] 267 | fn some_test { 268 | let mock = new_mock!(MyTrait #[derive(Clone)]#[other_attribute]); 269 | ... 270 | } 271 | ``` 272 | 273 | When the same mock setup code is shared across multiple tests we can place the mock creation code in a separate factory function, call it in the respective test cases, and modify it further (e.g. adding specific behaviours). 274 | To be able to do this we need to know the name of the created mock type. 275 | So far those types have been anonymous and a name has been chosen by the `new_mock!` command. 276 | It is possible to supply an explicit mock type name. 277 | ```Rust 278 | #[use_mocks] 279 | mod test_module { 280 | fn create_mock() -> mock::MyMockType { 281 | new_mock!(MyTrait #[some_attribute] for MyMockType) 282 | ... // define given/expec behaviours 283 | } 284 | 285 | #[test] 286 | fn some_test { 287 | let mock: mock::MyMockType = create_mock(); 288 | ... // define further test=specific given/expec behaviours 289 | } 290 | } 291 | 292 | ``` 293 | The created type is placed in a `mock` module which is automatically visible to all (sub-)modules and functions within the item annotated with `#[use_mocks]`. 294 | 295 | ### Defining behaviour with `given!` blocks 296 | 297 | After creating a mock object you can invoke the mocked traits' methods on it. 298 | Though as it is just a mock the called methods **will panic** as they don't know what to do. 299 | First you need to define behaviours of the object based on conditions on the method arguments. 300 | Following the terminology of [Behaviour Driven Development (BDD)](https://en.wikipedia.org/wiki/Behavior-driven_development) this is done with a `given!` block. 301 | It sets up the preconditions of the scenario we are testing. 302 | ```Rust 303 | given! { 304 | ::func |&(x, y)| x < y then_return 1 always; 305 | ... 306 | } 307 | ``` 308 | A `given!` block consists of several *given statements* with the following pattern. 309 | ```Rust 310 | given! { 311 | ::METHOD ARGUMENT_MATCHERS THEN REPEAT; 312 | ... 313 | } 314 | ``` 315 | The statement resembles [Universal Function Call Syntax](https://doc.rust-lang.org/book/first-edition/ufcs.html) with additional components: 316 | * `OBJECT` ... the mock object for which we define the pattern 317 | * `TRAIT` ... the mocked trait to which the `METHOD` belongs. Refering to the trait follows the same rules as `new_mock!`. *The UFC syntax is not optional and for now you must provide generic/associated type arguments in the same order as in the `new_mock!` statement which created the `OBJECT`.* 318 | * `METHOD` ... the method to which the behaviour belongs to 319 | * `ARGUMENT_MATCHERS` ... a precondition on the method arguments which must be fulfilled for the behaviour to be invoked 320 | * `THEN` ... defines what happens after the behaviours has been selected, e.g., return a constant value 321 | * `REPEAT` ... defines how often the behaviour can be matched before it becomes invalid 322 | 323 | When a method is invoked its given behaviours' preconditions are checked top-down and the first matching behaviour is selected. 324 | A given block is *not* a global definition and behaves as any other block/statement: 325 | If the control flow never enters the block the behaviours won't be added to the mock object. 326 | If a block is entered multiple times or if another block is reached, then its behaviours are appended to the current list of behaviours. 327 | 328 | As writing the full UFC syntax gets tiresome if many behaviours need to be defined for a mock object, a bit of syntactic sugar has been added. 329 | ```Rust 330 | given! { 331 | ::{ 332 | METHOD ARGUMENT_MATCHERS THEN REPEAT; 333 | METHOD ARGUMENT_MATCHERS THEN REPEAT; 334 | ... 335 | }; 336 | ... 337 | } 338 | ``` 339 | These behaviour blocks get rid of unnecessary duplication. 340 | Note that the semicolon at the end of the block is *not optional*. 341 | 342 | *Further note that mocking **static** methods is currently not supported!*. 343 | 344 | #### Argument patterns 345 | 346 | Preconditions on the method arguments can be defined in two forms: **per-argument** and **explicit**. 347 | 348 | ##### Per-Argument Patterns 349 | 350 | Most of the time per-argument patterns will be enough and are considered more readable. 351 | ```Rust 352 | given! { 353 | ::func(|&x| x == 2, |&y| y < 3.0) then_return 1 always; 354 | } 355 | ``` 356 | The argument matchers follow the closure syntax and its parameters are passed *by immutable reference* and must return a `bool` or something that implements `std::convert::Into`. 357 | Although we use closure syntax, **this is not a closure** meaning that you can't capture variables from the scope outside the given block. 358 | We will learn later how we can **bind** values from the outer scope to make them available to the given statements. 359 | 360 | If the `galvanic_assert_integration` feature is enabled then the matchers from `galvanic-assert` can be used instead of the closure syntax. 361 | See the introduction for some examples 362 | 363 | ##### Special Case: Void Patterns 364 | 365 | To match a method without arguments we have to use per-argument patterns though without passing a pattern. We refer to the form below as *void pattern*. 366 | ```Rust 367 | given! { 368 | ::func_without_args() then_return 1 always; 369 | } 370 | ``` 371 | 372 | ##### Explicit Patterns 373 | The second form receives all arguments at once in a tuple. 374 | ```Rust 375 | given! { 376 | ::func |&(x, y)| x < y then_return 1 always; 377 | } 378 | ``` 379 | Again the tuple of curried arguments is passed by reference. 380 | Note that we have to use `ref` when decomposing tuples with non-copyable objects (as in any other pattern in Rust). 381 | Observe the **lack of brackets** after `func` in this form. 382 | The brackets are used to distinguish between the two variants. 383 | 384 | **Note that *explicit patterns* cannot be used for methods without arguments.** 385 | 386 | #### Returning values 387 | 388 | Defining the behaviours' actions once selected is done in the `THEN` part of the statement. 389 | We can either return the value of a constant expression with `then_return`: 390 | ```RUST 391 | given! { 392 | ::func ... then_return (1+2).to_string() always; 393 | } 394 | ``` 395 | 396 | Or we compute a value based on the arguments of the function call with `then_return_from`. 397 | The arguments are again passed as a reference to curried argument tuple. 398 | Note again that we use closure syntax but we cannot capture variables from the outside scope. 399 | ```RUST 400 | given! { 401 | ::func ... then_return_from |&(x,y)| (x + y)*2 always; 402 | } 403 | ``` 404 | Or simply panic: 405 | ```RUST 406 | given! { 407 | ::func ... then_panic always; 408 | } 409 | ``` 410 | 411 | #### Repetition 412 | 413 | The final element of a behaviour is the number of *matching* repetitions before the behaviour is exhausted and will no longer match. 414 | The may either be `always` (as used up to now) or `times` followed by an integer expression. 415 | ```Rust 416 | let x: i32 = func() 417 | 418 | given! { 419 | ::func |&(x, y)| x < y then_return 1 times x+1; 420 | } 421 | ``` 422 | Contrary to argument matchers and then-expressions the `times` expression is evaluated in the context of the given block. 423 | 424 | #### Binding values from the outer scope 425 | 426 | Up until now argument matchers and then-expressions cannot refer to the outside context. 427 | The reason for this is mainly due to lifetime issues with references when actual closures would be passed to the mock objects. 428 | To get around these issues it is possible to **bind** values from the outside scope in a given block. 429 | ```Rust 430 | let x = 1; 431 | given! { 432 | bind value1: f64 = 12.23; 433 | bind value2: i32 = x*2; 434 | 435 | ::func |&(_, y)| y > bound.value2 then_return bound.value1 always; 436 | ::func |&(_, y)| y <= bound.value2 then_return_from |&(x, _)| x*bound.value1 always; 437 | } 438 | ``` 439 | Bind statements must occur before the given statements with the general form: 440 | ```Rust 441 | given! { 442 | bind VARIABLE: TYPE = EXPRESSION: 443 | ... 444 | } 445 | ``` 446 | Note that the type is not optional here. 447 | All variables defined with `bind` can later be accessed with a member of the `bound` variable. 448 | The bind expressions will be evaluated when the given block is entered. 449 | That also means if a given block is entered multiple times the bind statements will be reevaluated for the new behaviours. 450 | 451 | #### Behaviours for generic trait methods 452 | 453 | Be careful when you try to mock *generic methods* as below. 454 | ```Rust 455 | #[mockable] 456 | trait MyTrait { 457 | fn generic_func(x: T, y: F) -> T; 458 | } 459 | ... 460 | given! { 461 | ::generic_func |&(ref x, ref y)| ... then_return 1 always; 462 | } 463 | ``` 464 | The behaviour will be applied regardless of the actual types used. 465 | Meaning that besides the trait bounds defined on the type arguments you cannot use much else. 466 | We cannot assume, e.g., that `x` is always a `i32` although we might know that depending on the context. 467 | In such a case we must either detect the type ourselves or use `unsafe` casts. 468 | *This will likely change in future versions and get easier/more useful.* 469 | 470 | #### Behaviours for static trait methods 471 | 472 | *This is currently not supported but is high priority for one of the next versions.* 473 | 474 | 475 | ### Expecting interactions with `expect_interactions!` blocks 476 | 477 | Besides defining how a mock should act it is a common use case to want to know that some interactions, i.e., method calls, happened with the mock. 478 | This can be done with an expect block which works similar to given blocks. 479 | ```Rust 480 | expect_interactions! { 481 | ::func(|&x| x == 2, |&y| y < 12.23) times 2; 482 | } 483 | ``` 484 | Again the block consists of several expect statements with the following general form. 485 | ```Rust 486 | expect_interactions! { 487 | ::METHOD ARGUMENT_MATCHERS REPEAT; 488 | ... 489 | } 490 | ``` 491 | Trait blocks, argument matchers, bindings, and evaluation order work in the same way as given blocks. 492 | Repeat expressions support a few different options. 493 | Further a expect behaviour will never be exhausted. 494 | The expect statements only specify the testing order of the patterns, they do not specify the expected order of interactions. 495 | The order of interactions in a `expect_interactions` block is assumed to be arbitrary. 496 | Also only the first matching expect expression will be counted. 497 | Later expression whose argument matchers would also be satisfied with the same arguments will not be evaluated. 498 | 499 | *Specifying a fixed order is currently not supported.* 500 | 501 | The expectations are verified once the mock object is dropped or if `mock.verify()` is called. 502 | If the expected interactions did not happen as specified when verified the current thread will panic. 503 | If other interactions not matching any expect behaviour occured then they won't be seen as errors. 504 | 505 | #### Repetition 506 | 507 | The repeat expressions for a expect block can be one of the following. 508 | * `times EXPRESSION` ... states that *exactly* `EXPRESSION` number of matches must occur. 509 | * `never` ... states the interaction should never be encountered (same as `times 0`). 510 | * `at_least EXPRESSION` ... states that at least `EXPRESSION` (inclusive) number of matches must occur. 511 | * `at_most EXPRESSION` ... states that at most `EXPRESSION` (inclusive) number of matches must occur. 512 | * `between EXPRESSION1, EXPRESSION2` ... states that a number of matches in the inclusive range [`EXPRESSION1`, `EXPRESSION2`] should occur. 513 | 514 | ### The `Mock` interface 515 | 516 | All mocks support some basic methods for controlling the mock. 517 | * `should_verify_on_drop(bool)` ... if called with `false` verification on drop will be disabled and vice versa. 518 | * `reset_given_behaviours()` ... removes all given behaviours from the mock 519 | * `reset_expected_behaviours()` ... removes all expectations from the mock 520 | * `are_expected_behaviours_satisfied()` ... return `true` if all expectations are currently satisfied, `false` otherwise. 521 | * `verify()` ... panics if some expectaions are currently unsatisfied. 522 | -------------------------------------------------------------------------------- /src/acquire.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | macro_rules! acquire { 16 | ( $global_var: ident ) => { $global_var.lock().unwrap() } 17 | } 18 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use std::collections::HashMap; 17 | use std::sync::Mutex; 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct TraitInfo { 21 | pub safety: syn::Unsafety, 22 | pub generics: syn::Generics, 23 | pub generic_bounds: Vec, 24 | pub items: Vec 25 | } 26 | 27 | impl TraitInfo { 28 | pub fn new(safety: syn::Unsafety, 29 | generics: syn::Generics, 30 | generic_bounds: Vec, 31 | items: Vec) -> TraitInfo { 32 | TraitInfo { 33 | safety: safety, 34 | generics: generics, 35 | generic_bounds: generic_bounds, 36 | items: items 37 | } 38 | } 39 | } 40 | 41 | pub type MockableTraits = HashMap; 42 | lazy_static! { 43 | pub static ref MOCKABLE_TRAITS: Mutex = { 44 | Mutex::new(HashMap::new()) 45 | }; 46 | } 47 | 48 | 49 | pub struct RequestedMock { 50 | pub traits: Vec, 51 | pub attributes: Vec, 52 | pub maybe_type_name: Option 53 | } 54 | lazy_static! { 55 | pub static ref REQUESTED_MOCKS: Mutex> = { 56 | Mutex::new(Vec::new()) 57 | }; 58 | } 59 | 60 | 61 | pub struct Binding { 62 | pub block_id: usize, 63 | pub fields: Vec 64 | } 65 | 66 | pub struct BindingField { 67 | pub name: syn::Ident, 68 | pub ty: syn::Ty, 69 | pub initializer: syn::Expr 70 | } 71 | 72 | pub type Bindings = Vec; 73 | lazy_static! { 74 | pub static ref BINDINGS: Mutex = { 75 | Mutex::new(Vec::new()) 76 | }; 77 | } 78 | 79 | 80 | #[derive(Debug,Clone)] 81 | pub enum BehaviourMatcher { 82 | Void, 83 | Explicit(syn::Expr), 84 | PerArgument(Vec) 85 | } 86 | 87 | #[derive(Debug,PartialEq,Clone)] 88 | pub enum Return { 89 | FromValue(syn::Expr), 90 | FromCall(syn::Expr), 91 | FromSpy, 92 | Panic 93 | } 94 | 95 | #[derive(Debug,PartialEq,Clone)] 96 | pub enum GivenRepeat { 97 | Times(syn::Expr), 98 | Always 99 | } 100 | 101 | #[derive(Debug,Clone)] 102 | pub struct GivenStatement { 103 | pub block_id: usize, 104 | pub stmt_id: usize, 105 | pub mock_var: syn::Ident, 106 | pub ufc_trait: syn::Path, 107 | pub method: syn::Ident, 108 | pub matcher: BehaviourMatcher, 109 | pub return_stmt: Return, 110 | pub repeat: GivenRepeat, 111 | } 112 | 113 | impl GivenStatement { 114 | pub fn trait_name(&self) -> String { 115 | let ufc_trait = &self.ufc_trait; 116 | quote!(#ufc_trait).to_string() 117 | } 118 | 119 | pub fn method_name(&self) -> String { 120 | let method = &self.method; 121 | quote!(#method).to_string() 122 | } 123 | } 124 | 125 | impl ::std::fmt::Display for GivenStatement { 126 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { 127 | let match_expr = match &self.matcher { 128 | &BehaviourMatcher::Void => String::new(), 129 | &BehaviourMatcher::Explicit(ref expr) => format!(" {} ", quote!(#expr)), 130 | &BehaviourMatcher::PerArgument(ref exprs) => format!("({})", exprs.iter().map(|e| quote!(#e).to_string()).collect::>().join(", ")) 131 | }; 132 | let return_expr = match &self.return_stmt { 133 | &Return::FromValue(ref expr) => format!("then_return {}", quote!(#expr)), 134 | &Return::FromCall(ref expr) => format!("then_return_from {}", quote!(#expr)), 135 | &Return::FromSpy => panic!("Return::FromSpy is not supported yet"), 136 | &Return::Panic => String::from("then_panic") 137 | }; 138 | let repeat_expr = match &self.repeat { 139 | &GivenRepeat::Times(ref expr) => format!("times {}", quote!(#expr)), 140 | &GivenRepeat::Always => String::from("always") 141 | }; 142 | 143 | let ufc_trait = &self.ufc_trait; 144 | write!(f, "{}::{}{} {} {}", 145 | quote!(#ufc_trait), 146 | self.method, 147 | match_expr, 148 | return_expr, 149 | repeat_expr 150 | ) 151 | } 152 | } 153 | 154 | pub type GivenStatements = HashMap>; 155 | lazy_static! { 156 | pub static ref GIVEN_STATEMENTS: Mutex = { 157 | Mutex::new(HashMap::new()) 158 | }; 159 | } 160 | 161 | 162 | #[derive(Debug,PartialEq,Clone)] 163 | pub enum ExpectRepeat { 164 | Times(syn::Expr), 165 | AtLeast(syn::Expr), 166 | AtMost(syn::Expr), 167 | Between(syn::Expr, syn::Expr), 168 | } 169 | 170 | #[derive(Debug,Clone)] 171 | pub struct ExpectStatement { 172 | pub block_id: usize, 173 | pub stmt_id: usize, 174 | pub mock_var: syn::Ident, 175 | pub ufc_trait: syn::Path, 176 | pub method: syn::Ident, 177 | pub matcher: BehaviourMatcher, 178 | pub repeat: ExpectRepeat 179 | } 180 | 181 | impl ExpectStatement { 182 | pub fn trait_name(&self) -> String { 183 | let ufc_trait = &self.ufc_trait; 184 | quote!(#ufc_trait).to_string() 185 | } 186 | 187 | pub fn method_name(&self) -> String { 188 | let method = &self.method; 189 | quote!(#method).to_string() 190 | } 191 | } 192 | 193 | impl ::std::fmt::Display for ExpectStatement { 194 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { 195 | let match_expr = match &self.matcher { 196 | &BehaviourMatcher::Void => String::new(), 197 | &BehaviourMatcher::Explicit(ref expr) => format!(" {} ", quote!(#expr)), 198 | &BehaviourMatcher::PerArgument(ref exprs) => format!("({})", exprs.iter().map(|e| quote!(#e).to_string()).collect::>().join(", ")) 199 | }; 200 | let repeat_expr = match &self.repeat { 201 | &ExpectRepeat::Times(ref expr) => format!("times {}", quote!(#expr)), 202 | &ExpectRepeat::AtLeast(ref expr) => format!("at_least {}", quote!(#expr)), 203 | &ExpectRepeat::AtMost(ref expr) => format!("at_most {}", quote!(#expr)), 204 | &ExpectRepeat::Between(ref lb, ref ub) => format!("at_least {}, {}", quote!(#lb), quote!(#ub)), 205 | }; 206 | 207 | let ufc_trait = &self.ufc_trait; 208 | write!(f, "{}::{}{} {}", 209 | quote!(#ufc_trait), 210 | self.method, 211 | match_expr, 212 | repeat_expr 213 | ) 214 | } 215 | } 216 | 217 | pub type ExpectStatements = HashMap>; 218 | lazy_static! { 219 | pub static ref EXPECT_STATEMENTS: Mutex = { 220 | Mutex::new(HashMap::new()) 221 | }; 222 | } 223 | -------------------------------------------------------------------------------- /src/expect.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use syn::parse::*; 17 | 18 | use data::*; 19 | use generate::binding_implementer::implement_initialize_binding; 20 | 21 | named!(pub parse_bind -> BindingField, 22 | do_parse!( 23 | punct!("bind") >> name: call!(syn::parse::ident) >> 24 | punct!(":") >> ty: call!(syn::parse::ty) >> 25 | punct!("=") >> initializer: call!(syn::parse::expr) >> 26 | (BindingField { name, ty, initializer }) 27 | ) 28 | ); 29 | 30 | named!(pub parse_expect_interaction -> ExpectStatement, 31 | do_parse!( 32 | punct!("<") >> mock_var: call!(syn::parse::ident) >> keyword!("as") >> ufc_trait: call!(syn::parse::path) >> punct!(">") >> 33 | punct!("::") >> method: call!(syn::parse::ident) >> 34 | args: alt!( 35 | delimited!(punct!("("), separated_list!(punct!(","), syn::parse::expr), punct!(")")) => { 36 | |es: Vec<_>| 37 | if es.is_empty() { BehaviourMatcher::Void } 38 | else { BehaviourMatcher::PerArgument(es) } 39 | } 40 | | call!(syn::parse::expr) => { |e| BehaviourMatcher::Explicit(e) } 41 | ) >> 42 | repeat: alt!( preceded!(keyword!("times"), syn::parse::expr) => { |e| ExpectRepeat::Times(e) } 43 | | preceded!(keyword!("at_least"), syn::parse::expr) => { |e| ExpectRepeat::AtLeast(e) } 44 | | preceded!(keyword!("at_most"), syn::parse::expr) => { |e| ExpectRepeat::AtMost(e) } 45 | | preceded!(keyword!("between"), tuple!( call!(syn::parse::expr), preceded!(punct!(","), syn::parse::expr) )) => { |(e1, e2)| ExpectRepeat::Between(e1, e2) } 46 | | keyword!("never") => { |_| ExpectRepeat::Times(syn::parse::expr("0").expect("")) } 47 | ) >> 48 | (ExpectStatement { 49 | block_id: 0, 50 | stmt_id: 0, 51 | mock_var, 52 | ufc_trait, 53 | method, 54 | matcher: args, 55 | repeat 56 | }) 57 | ) 58 | ); 59 | 60 | named!(pub parse_expect_interactions -> (Vec, Vec), 61 | delimited!(tuple!(keyword!("expect_interactions"), punct!("!"), punct!("{")), 62 | tuple!( 63 | terminated_list!(punct!(";"), parse_bind), 64 | terminated_list!(punct!(";"), parse_expect_interaction) 65 | ), 66 | punct!("}") 67 | ) 68 | ); 69 | 70 | 71 | pub fn handle_expect_interactions(source: &str, absolute_position: usize) -> (String, String) { 72 | if let IResult::Done(remainder, (binding_fields, expect_definitions)) = parse_expect_interactions(source) { 73 | let mut statements = acquire!(EXPECT_STATEMENTS); 74 | 75 | let mut add_statements = Vec::new(); 76 | for (idx, mut stmt) in expect_definitions.into_iter().enumerate() { 77 | stmt.block_id = absolute_position; 78 | stmt.stmt_id = absolute_position + idx; 79 | let stmt_id = stmt.stmt_id; 80 | 81 | { 82 | let mock_var = &stmt.mock_var; 83 | let ufc_trait_name = stmt.trait_name(); 84 | let method_name = stmt.method_name(); 85 | 86 | let stmt_repr = format!("{}", stmt); 87 | add_statements.push(match &stmt.repeat { 88 | &ExpectRepeat::Times(ref expr) => quote!( #mock_var.add_expect_behaviour(#ufc_trait_name, #method_name, mock::ExpectBehaviour::with_times(#expr, #stmt_id, binding.clone(), #stmt_repr)); ), 89 | &ExpectRepeat::AtLeast(ref expr) => quote!( #mock_var.add_expect_behaviour(#ufc_trait_name, #method_name, mock::ExpectBehaviour::with_at_least(#expr, #stmt_id, binding.clone(), #stmt_repr)); ), 90 | &ExpectRepeat::AtMost(ref expr) => quote!( #mock_var.add_expect_behaviour(#ufc_trait_name, #method_name, mock::ExpectBehaviour::with_at_most(#expr, #stmt_id, binding.clone(), #stmt_repr)); ), 91 | &ExpectRepeat::Between(ref expr_lower, ref expr_upper) => quote!( #mock_var.add_expect_behaviour(#ufc_trait_name, #method_name, mock::ExpectBehaviour::with_between(#expr_lower, #expr_upper, #stmt_id, binding.clone(), #stmt_repr)); ), 92 | }); 93 | } 94 | statements.entry(stmt.ufc_trait.clone()) 95 | .or_insert_with(|| Vec::new()) 96 | .push(stmt); 97 | } 98 | 99 | let binding = Binding { 100 | block_id: absolute_position, 101 | fields: binding_fields 102 | }; 103 | let binding_initialization = implement_initialize_binding(&binding); 104 | acquire!(BINDINGS).push(binding); 105 | 106 | let given_block = quote! { 107 | let binding = std::rc::Rc::new(#binding_initialization); 108 | #(#add_statements)* 109 | }; 110 | 111 | return (given_block.to_string(), remainder.to_owned()); 112 | } else { panic!("Expecting a `expect_interactions!` definition: ::METHOD(MATCHER, ...) REPEAT; ..."); } 113 | } 114 | -------------------------------------------------------------------------------- /src/generate/behaviour.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use quote; 17 | 18 | use super::*; 19 | use data::GivenStatement; 20 | 21 | pub fn implement_given_behaviour() -> Vec { 22 | let behaviour_item = quote! { 23 | pub(crate) struct GivenBehaviour { 24 | stmt_id: usize, 25 | num_matches: std::cell::Cell, 26 | expected_matches: Option, 27 | bound: std::rc::Rc, 28 | stmt_repr: String 29 | } 30 | }; 31 | 32 | let behaviour_impl = quote! { 33 | #[allow(dead_code)] 34 | impl GivenBehaviour { 35 | pub fn with(stmt_id: usize, bound: std::rc::Rc, stmt_repr: &str) -> Self { 36 | Self { 37 | stmt_id: stmt_id, 38 | num_matches: std::cell::Cell::new(0), 39 | expected_matches: None, 40 | bound: bound, 41 | stmt_repr: stmt_repr.to_string() 42 | } 43 | } 44 | 45 | pub fn with_times(times: usize, stmt_id: usize, bound: std::rc::Rc, stmt_repr: &str) -> Self { 46 | Self { 47 | stmt_id: stmt_id, 48 | num_matches: std::cell::Cell::new(0), 49 | expected_matches: Some(times), 50 | bound: bound, 51 | stmt_repr: stmt_repr.to_string() 52 | } 53 | } 54 | 55 | pub fn matched(&self) { 56 | self.num_matches.set(self.num_matches.get() + 1); 57 | } 58 | 59 | pub fn is_saturated(&self) -> bool { 60 | match self.expected_matches { 61 | Some(limit) => self.num_matches.get() >= limit, 62 | None => false 63 | } 64 | } 65 | 66 | pub fn describe(&self) -> &str { 67 | &self.stmt_repr 68 | } 69 | } 70 | }; 71 | 72 | vec![behaviour_item, behaviour_impl] 73 | } 74 | 75 | pub fn implement_given_behaviour_matcher(statement: &GivenStatement) -> quote::Tokens { 76 | let return_expr = match &statement.return_stmt { 77 | &Return::FromValue(ref expr) => quote!{ #expr }, 78 | &Return::FromCall(ref expr) => quote!{ (#expr)(&curried_args) }, 79 | &Return::FromSpy => panic!("return_from_spy is not implemented yet."), 80 | &Return::Panic => quote!{ panic!("Panic by behaviour. Don't forget the towel.") } 81 | }; 82 | 83 | let match_expr = match statement.matcher { 84 | BehaviourMatcher::Void => quote!{ true }, 85 | BehaviourMatcher::Explicit(ref expr) => quote!{ (#expr).match_args(&curried_args) }, 86 | BehaviourMatcher::PerArgument(ref exprs) => { 87 | let mut arg_tokens = quote::Tokens::new(); 88 | arg_tokens.append("("); 89 | for idx in 0..exprs.len() { 90 | if idx >= 1 { 91 | arg_tokens.append("&&"); 92 | } 93 | let expr = exprs.get(idx).unwrap(); 94 | arg_tokens.append(quote!( (#expr) )); 95 | arg_tokens.append(format!(".match_args(&curried_args.{})", idx)); 96 | } 97 | arg_tokens.append(")"); 98 | arg_tokens 99 | } 100 | }; 101 | 102 | let stmt_id = statement.stmt_id; 103 | let return_value = syn::Ident::from("return_value"); 104 | let behaviour_idx = syn::Ident::from("idx"); 105 | let maybe_remove_idx = syn::Ident::from("maybe_remove_idx"); 106 | let binding_type = binding_name_for(statement.block_id); 107 | quote! { 108 | if behaviour.stmt_id == #stmt_id { 109 | let bound = behaviour.bound.downcast_ref::<#binding_type>() 110 | .expect("galvanic_mock internal error: unable to downcast binding type"); 111 | use std::convert::Into; 112 | if (#match_expr).into() { 113 | behaviour.matched(); 114 | #return_value = Some(#return_expr); 115 | if behaviour.is_saturated() { 116 | #maybe_remove_idx = Some(#behaviour_idx); 117 | } 118 | break; 119 | } 120 | } 121 | } 122 | } 123 | 124 | 125 | pub fn implement_expect_behaviour() -> Vec { 126 | let behaviour_item = quote! { 127 | pub(crate) struct ExpectBehaviour { 128 | stmt_id: usize, 129 | num_matches: std::cell::Cell, 130 | expected_min_matches: Option, 131 | expected_max_matches: Option, 132 | in_order: Option, 133 | bound: std::rc::Rc, 134 | stmt_repr: String 135 | } 136 | }; 137 | 138 | let behaviour_impl = quote! { 139 | #[allow(dead_code)] 140 | impl ExpectBehaviour { 141 | pub fn with_times(times: usize, stmt_id: usize, bound: std::rc::Rc, stmt_repr: &str) -> Self { 142 | Self { 143 | stmt_id: stmt_id, 144 | num_matches: std::cell::Cell::new(0), 145 | expected_min_matches: Some(times), 146 | expected_max_matches: Some(times), 147 | in_order: None, 148 | bound: bound, 149 | stmt_repr: stmt_repr.to_string() 150 | } 151 | } 152 | 153 | pub fn with_at_least(at_least_times: usize, stmt_id: usize, bound: std::rc::Rc, stmt_repr: &str) -> Self { 154 | Self { 155 | stmt_id: stmt_id, 156 | num_matches: std::cell::Cell::new(0), 157 | expected_min_matches: Some(at_least_times), 158 | expected_max_matches: None, 159 | in_order: None, 160 | bound: bound, 161 | stmt_repr: stmt_repr.to_string() 162 | } 163 | } 164 | 165 | pub fn with_at_most(at_most_times: usize, stmt_id: usize, bound: std::rc::Rc, stmt_repr: &str) -> Self { 166 | Self { 167 | stmt_id: stmt_id, 168 | num_matches: std::cell::Cell::new(0), 169 | expected_min_matches: None, 170 | expected_max_matches: Some(at_most_times), 171 | in_order: None, 172 | bound: bound, 173 | stmt_repr: stmt_repr.to_string() 174 | } 175 | } 176 | 177 | pub fn with_between(at_least_times: usize, at_most_times: usize, stmt_id: usize, bound: std::rc::Rc, stmt_repr: &str) -> Self { 178 | Self { 179 | stmt_id: stmt_id, 180 | num_matches: std::cell::Cell::new(0), 181 | expected_min_matches: Some(at_least_times), 182 | expected_max_matches: Some(at_most_times), 183 | in_order: None, 184 | bound: bound, 185 | stmt_repr: stmt_repr.to_string() 186 | } 187 | } 188 | 189 | pub fn matched(&self) { 190 | self.num_matches.set(self.num_matches.get() + 1); 191 | } 192 | 193 | pub fn is_saturated(&self) -> bool { 194 | self.expected_min_matches.unwrap_or(0) <= self.num_matches.get() 195 | && self.num_matches.get() <= self.expected_max_matches.unwrap_or(std::usize::MAX) 196 | } 197 | 198 | pub fn describe(&self) -> &str { 199 | &self.stmt_repr 200 | } 201 | } 202 | }; 203 | 204 | vec![behaviour_item, behaviour_impl] 205 | } 206 | 207 | pub fn implement_expect_behaviour_matcher(statement: &ExpectStatement) -> quote::Tokens { 208 | let match_expr = match statement.matcher { 209 | BehaviourMatcher::Void => quote!{ true }, 210 | BehaviourMatcher::Explicit(ref expr) => quote!{ (#expr).match_args(&curried_args) }, 211 | BehaviourMatcher::PerArgument(ref exprs) => { 212 | let mut arg_tokens = quote::Tokens::new(); 213 | arg_tokens.append("("); 214 | for idx in 0..exprs.len() { 215 | if idx >= 1 { 216 | arg_tokens.append("&&"); 217 | } 218 | let expr = exprs.get(idx).unwrap(); 219 | arg_tokens.append(quote!( (#expr) )); 220 | arg_tokens.append(format!(".match_args(&curried_args.{})", idx)); 221 | } 222 | arg_tokens.append(")"); 223 | arg_tokens 224 | } 225 | }; 226 | 227 | let stmt_id = statement.stmt_id; 228 | let binding_type = binding_name_for(statement.block_id); 229 | quote! { 230 | if behaviour.stmt_id == #stmt_id { 231 | let bound = behaviour.bound.downcast_ref::<#binding_type>() 232 | .expect("galvanic_mock internal error: unable to downcast binding type"); 233 | use std::convert::Into; 234 | if (#match_expr).into() { 235 | behaviour.matched(); 236 | break; 237 | } 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/generate/binding_implementer.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use quote; 17 | use data::Binding; 18 | 19 | pub fn binding_name_for(block_id: usize) -> syn::Ident { 20 | syn::Ident::from(format!("Binding{}", block_id)) 21 | } 22 | 23 | pub fn implement_bindings(bindings: &[Binding]) -> Vec { 24 | bindings.into_iter().map(|binding| implement_binding(binding)).collect::>() 25 | } 26 | 27 | fn implement_binding(binding: &Binding) -> quote::Tokens { 28 | let binding_name = binding_name_for(binding.block_id); 29 | let fields = binding.fields.iter().map(|field| { 30 | let name = &field.name; 31 | let ty = &field.ty; 32 | quote!(pub #name: #ty) 33 | }).collect::>(); 34 | 35 | quote!{ 36 | #[derive(Clone)] 37 | pub(crate) struct #binding_name { 38 | #(#fields),* 39 | } 40 | } 41 | } 42 | 43 | pub fn implement_initialize_binding(binding: &Binding) -> quote::Tokens { 44 | let binding_name = binding_name_for(binding.block_id); 45 | let field_initializers = binding.fields.iter().map(|field| { 46 | let name = &field.name; 47 | let initializer = &field.initializer; 48 | quote!(#name: #initializer) 49 | }).collect::>(); 50 | 51 | quote!{ 52 | mock::#binding_name { 53 | #(#field_initializers),* 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/generate/mock_struct_implementer.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use quote; 17 | 18 | /// Generates mock structs and implementations. 19 | pub struct MockStructImplementer<'a> { 20 | /// The name of the mock type 21 | mock_type_name: &'a syn::Ident, 22 | /// The attributes which should be applied to the generated mock 23 | attributes: &'a [syn::Attribute], 24 | } 25 | 26 | impl<'a> MockStructImplementer<'a> { 27 | /// Create a new mock struct. 28 | pub fn for_(mock_type_name: &'a syn::Ident, attributes: &'a [syn::Attribute]) -> Self { 29 | MockStructImplementer { mock_type_name, attributes } 30 | } 31 | 32 | /// Generate the struct definition of the mock and the methods for creating/interacting with the mock. 33 | pub fn implement(&self) -> Vec { 34 | let mock_type_name = &self.mock_type_name; 35 | let attributes = self.attributes; 36 | 37 | let mock_struct = quote! { 38 | #(#attributes)* 39 | pub(crate) struct #mock_type_name { 40 | given_behaviours: std::cell::RefCell>>, 41 | expect_behaviours: std::cell::RefCell>>, 42 | verify_on_drop: bool, 43 | } 44 | }; 45 | 46 | let mock_impl = quote! { 47 | impl #mock_type_name { 48 | pub fn new() -> Self { 49 | Self { 50 | given_behaviours: std::cell::RefCell::new(std::collections::HashMap::new()), 51 | expect_behaviours: std::cell::RefCell::new(std::collections::HashMap::new()), 52 | verify_on_drop: true, 53 | } 54 | } 55 | 56 | pub fn should_verify_on_drop(&mut self, flag: bool) { self.verify_on_drop = flag; } 57 | 58 | #[allow(dead_code)] 59 | pub fn add_given_behaviour(&self, requested_trait: &'static str, method: &'static str, behaviour: GivenBehaviour) { 60 | self.given_behaviours.borrow_mut() 61 | .entry((requested_trait, method)) 62 | .or_insert_with(|| Vec::new()) 63 | .push(behaviour); 64 | } 65 | 66 | #[allow(dead_code)] 67 | pub fn reset_given_behaviours(&mut self) { 68 | self.given_behaviours.borrow_mut().clear(); 69 | } 70 | 71 | #[allow(dead_code)] 72 | pub fn add_expect_behaviour(&self, requested_trait: &'static str, method: &'static str, behaviour: ExpectBehaviour) { 73 | self.expect_behaviours.borrow_mut() 74 | .entry((requested_trait, method)) 75 | .or_insert_with(|| Vec::new()) 76 | .push(behaviour); 77 | } 78 | 79 | #[allow(dead_code)] 80 | pub fn reset_expected_behaviours(&mut self) { 81 | self.expect_behaviours.borrow_mut().clear(); 82 | } 83 | 84 | #[allow(dead_code)] 85 | pub fn are_expected_behaviours_satisfied(&self) -> bool { 86 | let mut unsatisfied_messages: Vec = Vec::new(); 87 | for behaviour in self.expect_behaviours.borrow().values().flat_map(|vs| vs) { 88 | if !behaviour.is_saturated() { 89 | unsatisfied_messages.push(format!("Behaviour unsatisfied: {}", behaviour.describe())); 90 | } 91 | } 92 | 93 | if !unsatisfied_messages.is_empty() { 94 | for message in unsatisfied_messages { 95 | println!("{}", message); 96 | } 97 | false 98 | } else { true } 99 | } 100 | 101 | #[allow(dead_code)] 102 | pub fn verify(&self) { 103 | if !self.are_expected_behaviours_satisfied() && !std::thread::panicking() { 104 | panic!("There are unsatisfied expected behaviours for mocked traits."); 105 | } 106 | } 107 | } 108 | }; 109 | 110 | let mock_drop_impl = quote! { 111 | impl std::ops::Drop for #mock_type_name { 112 | fn drop(&mut self) { 113 | if self.verify_on_drop { 114 | self.verify(); 115 | } 116 | } 117 | } 118 | }; 119 | 120 | vec![mock_struct, mock_impl, mock_drop_impl] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/generate/mod.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | pub mod binding_implementer; 16 | mod type_param_mapper; 17 | mod mock_struct_implementer; 18 | mod trait_implementer; 19 | mod behaviour; 20 | 21 | use syn; 22 | use quote; 23 | 24 | use ::generate::binding_implementer::*; 25 | use ::generate::behaviour::*; 26 | use ::generate::type_param_mapper::*; 27 | use ::generate::mock_struct_implementer::*; 28 | use ::generate::trait_implementer::*; 29 | use data::*; 30 | 31 | /// Generates all mock structs and implementations. 32 | pub fn handle_generate_mocks() -> Vec { 33 | let mockable_traits = acquire!(MOCKABLE_TRAITS); 34 | let mut requested_mocks = acquire!(REQUESTED_MOCKS); 35 | let mut given_statements = acquire!(GIVEN_STATEMENTS); 36 | let mut expect_statements = acquire!(EXPECT_STATEMENTS); 37 | let mut bindings = acquire!(BINDINGS); 38 | 39 | let mut tokens = implement_bindings(&bindings); 40 | tokens.extend(implement_argmatcher()); 41 | tokens.extend(implement_given_behaviour()); 42 | tokens.extend(implement_expect_behaviour()); 43 | 44 | for requested_mock in requested_mocks.iter() { 45 | let inst_traits = requested_mock.traits.iter().map(|trait_ty| create_instantiated_traits(trait_ty, &mockable_traits)).collect::>(); 46 | tokens.extend(handle_generate_mock(requested_mock.maybe_type_name.as_ref().expect("Internal error: requested mock has no type name"), 47 | &requested_mock.attributes, 48 | &inst_traits, 49 | &given_statements, 50 | &expect_statements)); 51 | } 52 | 53 | bindings.clear(); 54 | requested_mocks.clear(); 55 | given_statements.clear(); 56 | expect_statements.clear(); 57 | 58 | tokens 59 | } 60 | 61 | fn create_instantiated_traits(trait_path: &syn::Path, mockable_traits: &MockableTraits) 62 | -> InstantiatedTrait { 63 | let trait_info = mockable_traits 64 | .get(&strip_generics(trait_path.clone())) 65 | .expect(&format!("All mocked traits must be defined using 'mockable!': `{}` not found in {}", 66 | quote!(#trait_path).to_string(), 67 | mockable_traits.keys().map(|k| quote!(#k).to_string()).collect::>().join(", "))); 68 | let mut mapper = TypeParamMapper::new(); 69 | { 70 | let generics: &syn::Generics = &trait_info.generics; 71 | let instantiated_params = extract_parameterized_types_from_trait_use(trait_path); 72 | 73 | for (param, instantiated) in generics.ty_params.iter().zip(instantiated_params) { 74 | mapper.add_mapping(param.ident.clone(), instantiated); 75 | } 76 | } 77 | 78 | InstantiatedTrait { 79 | trait_ty: trait_path.clone(), 80 | info: trait_info.clone(), 81 | mapper: mapper 82 | } 83 | } 84 | 85 | fn strip_generics(mut path_with_generics: syn::Path) -> syn::Path { 86 | let mut segment = path_with_generics.segments.pop().unwrap(); 87 | segment.parameters = syn::PathParameters::none(); 88 | path_with_generics.segments.push(segment); 89 | path_with_generics 90 | } 91 | 92 | /// Generates a mock implementation for a 93 | /// 94 | /// The following elements are generated: 95 | /// * struct definition 96 | /// * mock interface implementation 97 | /// * mocked trait implementations 98 | /// for a mock of a given name, and a set of requested traits. 99 | /// 100 | /// All mock types requested by a `new_mock!` invocations are generated with this function. 101 | /// 102 | /// # Paramters 103 | /// * `mock_type_name` - The name of the generated mock type 104 | /// * `trait_tys` - The (generic) trait types which are requested for the mock 105 | fn handle_generate_mock(mock_type_name: &syn::Ident, 106 | attributes: &[syn::Attribute], 107 | requested_traits: &[InstantiatedTrait], 108 | given_statements: &GivenStatements, 109 | expect_statements: &ExpectStatements 110 | ) -> Vec { 111 | let mock_implementer = MockStructImplementer::for_(mock_type_name, attributes); 112 | let mut mock = mock_implementer.implement(); 113 | 114 | let empty_given = Vec::new(); 115 | let empty_expect = Vec::new(); 116 | for inst_trait in requested_traits { 117 | let given_statements_for_trait = given_statements.get(&inst_trait.trait_ty) 118 | .unwrap_or(&empty_given); 119 | let expect_statements_for_trait = expect_statements.get(&inst_trait.trait_ty) 120 | .unwrap_or(&empty_expect); 121 | mock.push(TraitImplementer::for_(mock_type_name, 122 | inst_trait, 123 | given_statements_for_trait, 124 | expect_statements_for_trait 125 | ).implement()); 126 | } 127 | 128 | mock 129 | } 130 | 131 | fn extract_parameterized_types_from_trait_use(trait_ty: &syn::Path) -> Vec { 132 | match trait_ty.segments[0].parameters { 133 | syn::PathParameters::AngleBracketed(ref data) => data.types.clone(), 134 | _ => panic!("Type parameter extraction only works for angle-bracketed types.") 135 | } 136 | } 137 | 138 | fn implement_argmatcher() -> Vec { 139 | let argmatcher_trait = quote! { 140 | pub trait ArgMatcher<'a, T:'a> { 141 | fn match_args(&self, actual: &'a T) -> bool; 142 | } 143 | }; 144 | 145 | let argmatcher_impl = quote! { 146 | impl<'a, T:'a, F> ArgMatcher<'a,T> for F 147 | where F: Fn(&'a T) -> bool { 148 | fn match_args(&self, actual: &'a T) -> bool { 149 | self(actual) 150 | } 151 | } 152 | }; 153 | 154 | if cfg!(feature = "galvanic_assert_integration") { 155 | let matcher_argmatcher_impl = quote! { 156 | impl<'a, T:'a> ArgMatcher<'a,T> for Box<::galvanic_assert::Matcher<'a, T> + 'a> { 157 | fn match_args(&self, actual: &'a T) -> bool { 158 | self.check(actual).into() 159 | } 160 | } 161 | }; 162 | 163 | vec![argmatcher_trait, argmatcher_impl, matcher_argmatcher_impl] 164 | } else { vec![argmatcher_trait, argmatcher_impl] } 165 | } 166 | 167 | pub fn typed_arguments_for_method_sig(signature: &syn::MethodSig, mapper: &TypeParamMapper) -> Vec { 168 | let mut arg_idx = 1; 169 | signature.decl.inputs.iter().map(|arg| { 170 | let arg_name = syn::Ident::from(format!("arg{}", arg_idx)); 171 | match arg { 172 | &syn::FnArg::Captured(_, ref ty) => { 173 | let inst_ty = mapper.instantiate_from_ty(ty); 174 | arg_idx += 1; 175 | quote!(#arg_name: #inst_ty) 176 | }, 177 | &syn::FnArg::Ignored(ref ty) => { 178 | let inst_ty = mapper.instantiate_from_ty(ty); 179 | arg_idx += 1; 180 | quote!(#arg_name: #inst_ty) 181 | } 182 | _ => quote!(#arg) 183 | }}).collect::>() 184 | } 185 | 186 | 187 | #[derive(Clone,Debug)] 188 | pub struct InstantiatedTrait { 189 | trait_ty: syn::Path, 190 | info: TraitInfo, 191 | mapper: TypeParamMapper, 192 | } 193 | -------------------------------------------------------------------------------- /src/generate/trait_implementer.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use quote; 17 | use quote::ToTokens; 18 | 19 | use std; 20 | 21 | use super::InstantiatedTrait; 22 | use super::typed_arguments_for_method_sig; 23 | use super::behaviour::*; 24 | use data::*; 25 | 26 | pub struct TraitImplementer<'a> { 27 | mock_type_name: &'a syn::Ident, 28 | instantiated_trait: &'a InstantiatedTrait, 29 | given_statements: &'a [GivenStatement], 30 | expect_statements: &'a [ExpectStatement] 31 | } 32 | 33 | impl<'a> TraitImplementer<'a> { 34 | pub fn for_(mock_type_name: &'a syn::Ident, 35 | instantiated_trait: &'a InstantiatedTrait, 36 | given_statements_for_trait: &'a [GivenStatement], 37 | expect_statements_for_trait: &'a [ExpectStatement] 38 | ) -> TraitImplementer<'a> { 39 | TraitImplementer { 40 | mock_type_name: mock_type_name, 41 | instantiated_trait: instantiated_trait, 42 | given_statements: given_statements_for_trait, 43 | expect_statements: expect_statements_for_trait 44 | } 45 | } 46 | 47 | pub fn implement(&self) -> quote::Tokens { 48 | let methods: Vec<_> = self.instantiated_trait.info.items.iter().flat_map(|item| 49 | self.implement_mocked_method(item).into_iter() 50 | ).collect(); 51 | 52 | let lifetime_defs = &self.instantiated_trait.info.generics.lifetimes; 53 | let lifetimes = lifetime_defs.into_iter().map(|def| def.lifetime.clone()).collect::>(); 54 | 55 | let mock_type_name = self.mock_type_name.clone(); 56 | let mut trait_ty = self.instantiated_trait.trait_ty.clone(); 57 | 58 | let bindings = TraitImplementer::extract_associated_types(&mut trait_ty, lifetimes); 59 | let assoc_types = bindings.into_iter().map(|syn::TypeBinding{ref ident, ref ty}| quote!(#ident = #ty)).collect::>(); 60 | 61 | quote! { 62 | impl<#(#lifetime_defs),*> #trait_ty for #mock_type_name{ 63 | #(type #assoc_types;)* 64 | #(#methods)* 65 | } 66 | } 67 | } 68 | 69 | fn extract_associated_types(trait_ty: &mut syn::Path, lifetimes: Vec) -> Vec { 70 | let ty = trait_ty.segments.last_mut().expect("A type path without segment is not valid."); 71 | if let &mut syn::PathParameters::AngleBracketed(ref mut params) = &mut ty.parameters { 72 | params.lifetimes = lifetimes; 73 | std::mem::replace(&mut params.bindings, Vec::new()) 74 | } else { Vec::new() } 75 | } 76 | 77 | fn implement_mocked_method(&self, item: &syn::TraitItem) -> Option { 78 | let mut tokens = quote::Tokens::new(); 79 | if let &syn::TraitItemKind::Method(ref signature, _) = &item.node { 80 | signature.decl.inputs.iter().find(|arg| match arg { 81 | &&syn::FnArg::SelfValue(..) => true, 82 | &&syn::FnArg::SelfRef(..) => true, 83 | _ => false 84 | }).expect("Static methods are not supported yet."); 85 | 86 | let func_name = &item.ident; 87 | 88 | // generate fn signature/header 89 | signature.constness.to_tokens(&mut tokens); 90 | signature.unsafety.to_tokens(&mut tokens); 91 | signature.abi.to_tokens(&mut tokens); 92 | tokens.append("fn"); 93 | func_name.to_tokens(&mut tokens); 94 | signature.generics.to_tokens(&mut tokens); 95 | tokens.append("("); 96 | 97 | let args = typed_arguments_for_method_sig(signature, &self.instantiated_trait.mapper); 98 | tokens.append_separated(&args, ","); 99 | 100 | tokens.append(")"); 101 | if let syn::FunctionRetTy::Ty(ref ty) = signature.decl.output { 102 | tokens.append("->"); 103 | self.instantiated_trait.mapper.instantiate_from_ty(ty).to_tokens(&mut tokens); 104 | } 105 | signature.generics.where_clause.to_tokens(&mut tokens); 106 | tokens.append("{"); 107 | 108 | let args = self.generate_argument_names(&signature.decl.inputs); 109 | 110 | let given_behaviour_impls = self.given_statements.iter() 111 | .filter(|stmt| stmt.method == item.ident) 112 | .map(|stmt| implement_given_behaviour_matcher(stmt)) 113 | .collect::>(); 114 | let expect_behaviour_impls = self.expect_statements.iter() 115 | .filter(|stmt| stmt.method == item.ident) 116 | .map(|stmt| implement_expect_behaviour_matcher(stmt)) 117 | .collect::>(); 118 | 119 | let trait_ty = &self.instantiated_trait.trait_ty; 120 | let trait_name = quote!(#trait_ty).to_string(); 121 | let method_name = func_name.to_string(); 122 | 123 | tokens.append(quote!{ 124 | let curried_args = (#(#args,)*); 125 | for behaviour in self.expect_behaviours.borrow_mut().entry((#trait_name, #method_name)).or_insert_with(|| Vec::new()).iter() { 126 | #( 127 | #expect_behaviour_impls 128 | )* 129 | } 130 | 131 | let mut maybe_remove_idx = None; 132 | let mut return_value = None; 133 | let mut all_given_behaviours_ref = self.given_behaviours.borrow_mut(); 134 | let given_behaviours = all_given_behaviours_ref.entry((#trait_name, #method_name)).or_insert_with(|| Vec::new()); 135 | for (idx, behaviour) in given_behaviours.iter().enumerate() { 136 | #( 137 | #given_behaviour_impls 138 | )* 139 | } 140 | 141 | if let Some(idx) = maybe_remove_idx { 142 | if (&given_behaviours[idx] as &GivenBehaviour).is_saturated() { 143 | given_behaviours.remove(idx); 144 | } 145 | } 146 | 147 | if let Some(value) = return_value { 148 | return value; 149 | } 150 | panic!("No matching given! statement found among the remaining ones: {}", 151 | given_behaviours.iter().map(|behaviour| format!("\n\t{}", behaviour.describe())).collect::() 152 | ) 153 | }); 154 | 155 | tokens.append("}"); 156 | return Some(tokens); 157 | } 158 | None 159 | } 160 | 161 | fn generate_argument_names(&self, func_inputs: &[syn::FnArg]) -> Vec { 162 | let mut arg_names = Vec::new(); 163 | let mut arg_idx = 1; 164 | for arg in func_inputs { 165 | match arg { 166 | &syn::FnArg::Captured(..) | &syn::FnArg::Ignored(..) => { 167 | arg_names.push(syn::Ident::from(format!("arg{}", arg_idx))); 168 | arg_idx += 1; 169 | }, 170 | _ => {} 171 | } 172 | } 173 | arg_names 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/generate/type_param_mapper.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use quote; 17 | 18 | /// Maps type parameters occuring in a generic trait definitions to types. 19 | #[derive(Clone, Debug)] 20 | pub struct TypeParamMapper { 21 | type_param_and_type: Vec<(String, String)> 22 | } 23 | 24 | impl TypeParamMapper { 25 | pub fn new() -> TypeParamMapper { 26 | TypeParamMapper { type_param_and_type: Vec::new() } 27 | } 28 | 29 | pub fn add_mapping(&mut self, param: syn::Ident, ty: syn::Ty) { 30 | self.type_param_and_type.push((param.to_string(), quote!{ #ty }.to_string())); 31 | } 32 | 33 | /// Creates an instantiated/full type from `quote::Tokens` representing a generic type. 34 | /// 35 | /// The stored type parameter mappings a used to replace the type parameters 36 | /// in the generic type, e.g., `A -> i32` instantiates `MyFoo` to `MyFoo`. 37 | /// Note that the type parameters may not occur on the first level, 38 | /// e.g., `MyFoo>` maps to `MyFoo>`. 39 | /// 40 | /// Note that this uses a heuristic. The algorithm greedily replaces all 41 | /// whitespace separated occurances of a known type parameter with 42 | /// the associated type. 43 | /// 44 | /// # Panics 45 | /// Panics is the instantiated type cannot be parsed.` 46 | pub fn instantiate_from_ty_token(&self, generic_ty_tokens: "e::Tokens) -> syn::Ty { 47 | syn::parse::ty( 48 | &generic_ty_tokens.to_string().split_whitespace() 49 | .map(|x| { 50 | for &(ref param, ref ty) in &self.type_param_and_type { 51 | if x == param { return ty.to_string(); } 52 | } 53 | x.to_string() + " " 54 | }).collect::() 55 | ).expect(&format!("Unable to instantiate generic type `{:?}` with: {:?}", 56 | generic_ty_tokens, self.type_param_and_type 57 | )) 58 | } 59 | 60 | /// Creates an instantiated/full type from a generic type. 61 | pub fn instantiate_from_ty(&self, generic_ty: &syn::Ty) -> syn::Ty { 62 | self.instantiate_from_ty_token("e!{ #generic_ty }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/given.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use syn::parse::*; 17 | 18 | use data::*; 19 | use generate::binding_implementer::implement_initialize_binding; 20 | 21 | 22 | named!(pub parse_bind -> BindingField, 23 | do_parse!( 24 | punct!("bind") >> name: call!(syn::parse::ident) >> 25 | punct!(":") >> ty: call!(syn::parse::ty) >> 26 | punct!("=") >> initializer: call!(syn::parse::expr) >> 27 | (BindingField { name, ty, initializer }) 28 | ) 29 | ); 30 | 31 | named!(parse_given_func -> (syn::Ident, BehaviourMatcher, Return, GivenRepeat), 32 | do_parse!( 33 | method: call!(syn::parse::ident) >> 34 | args: alt!( 35 | delimited!(punct!("("), separated_list!(punct!(","), syn::parse::expr), punct!(")")) => { 36 | |es: Vec<_>| 37 | if es.is_empty() { BehaviourMatcher::Void } 38 | else { BehaviourMatcher::PerArgument(es) } 39 | } 40 | | call!(syn::parse::expr) => { |e| BehaviourMatcher::Explicit(e) } 41 | ) >> 42 | return_stmt: alt!( preceded!(keyword!("then_return"), syn::parse::expr) => { |e| Return::FromValue(e) } 43 | | preceded!(keyword!("then_return_from"), syn::parse::expr) => { |e| Return::FromCall(e) } 44 | | preceded!(keyword!("then_return_ref"), syn::parse::expr) => { |e| Return::FromValue(e) } 45 | | preceded!(keyword!("then_return_ref_from"), syn::parse::expr) => { |e| Return::FromCall(e) } 46 | | keyword!("then_spy_on_object") => { |_| Return::FromSpy } 47 | | keyword!("then_panic") => { |_| Return::Panic } 48 | ) >> 49 | repeat: alt!( preceded!(keyword!("times"), syn::parse::expr) => { |e| GivenRepeat::Times(e) } 50 | | keyword!("always") => { |_| GivenRepeat::Always } 51 | ) >> 52 | (method, args, return_stmt, repeat) 53 | ) 54 | ); 55 | 56 | named!(pub parse_given -> Vec, 57 | do_parse!( 58 | punct!("<") >> mock_var: call!(syn::parse::ident) >> keyword!("as") >> ufc_trait: call!(syn::parse::path) >> punct!(">") >> 59 | punct!("::") >> 60 | func: parse_given_func >> 61 | (vec![GivenStatement { 62 | block_id: 0, 63 | stmt_id: 0, 64 | mock_var, 65 | ufc_trait, 66 | method: func.0, 67 | matcher: func.1, 68 | return_stmt: func.2, 69 | repeat: func.3 70 | }]) 71 | ) 72 | ); 73 | 74 | named!(pub parse_given_trait_block -> Vec, 75 | do_parse!( 76 | punct!("<") >> mock_var: call!(syn::parse::ident) >> keyword!("as") >> ufc_trait: call!(syn::parse::path) >> punct!(">") >> 77 | punct!("::") >> punct!("{") >> 78 | statements: terminated_list!(punct!(";"), do_parse!( 79 | func: parse_given_func >> 80 | (GivenStatement { 81 | block_id: 0, 82 | stmt_id: 0, 83 | mock_var: mock_var.clone(), 84 | ufc_trait: ufc_trait.clone(), 85 | method: func.0, 86 | matcher: func.1, 87 | return_stmt: func.2, 88 | repeat: func.3 89 | }) 90 | )) >> punct!("}") >> 91 | (statements) 92 | ) 93 | ); 94 | 95 | named!(pub parse_givens -> (Vec, Vec), 96 | delimited!(tuple!(keyword!("given"), punct!("!"), punct!("{")), 97 | tuple!( 98 | terminated_list!(punct!(";"), parse_bind), 99 | map!(terminated_list!(punct!(";"), alt!(parse_given | parse_given_trait_block)), 100 | |statements_list: Vec>| statements_list.into_iter().flat_map(|stmts| stmts.into_iter()).collect::>() 101 | ) 102 | ), 103 | punct!("}") 104 | ) 105 | ); 106 | 107 | 108 | pub fn handle_given(source: &str, absolute_position: usize) -> (String, String) { 109 | if let IResult::Done(remainder, (binding_fields, given_definitions)) = parse_givens(source) { 110 | let mut statements = acquire!(GIVEN_STATEMENTS); 111 | 112 | let mut add_statements = Vec::new(); 113 | for (idx, mut stmt) in given_definitions.into_iter().enumerate() { 114 | stmt.block_id = absolute_position; 115 | stmt.stmt_id = absolute_position + idx; 116 | let stmt_id = stmt.stmt_id; 117 | 118 | { 119 | let mock_var = &stmt.mock_var; 120 | let ufc_trait_name = stmt.trait_name(); 121 | let method_name = stmt.method_name(); 122 | 123 | let stmt_repr = format!("{}", stmt); 124 | add_statements.push(match &stmt.repeat { 125 | &GivenRepeat::Always => quote!( #mock_var.add_given_behaviour(#ufc_trait_name, #method_name, mock::GivenBehaviour::with(#stmt_id, binding.clone(), #stmt_repr)); ), 126 | &GivenRepeat::Times(ref expr) => quote!( #mock_var.add_given_behaviour(#ufc_trait_name, #method_name, mock::GivenBehaviour::with_times(#expr, #stmt_id, binding.clone(), #stmt_repr)); ), 127 | }); 128 | } 129 | 130 | statements.entry(stmt.ufc_trait.clone()) 131 | .or_insert_with(|| Vec::new()) 132 | .push(stmt); 133 | } 134 | 135 | let binding = Binding { 136 | block_id: absolute_position, 137 | fields: binding_fields 138 | }; 139 | let binding_initialization = implement_initialize_binding(&binding); 140 | acquire!(BINDINGS).push(binding); 141 | 142 | let given_block = quote! { 143 | let binding = std::rc::Rc::new(#binding_initialization); 144 | #(#add_statements)* 145 | }; 146 | 147 | return (given_block.to_string(), remainder.to_owned()); 148 | } else { panic!("Expecting a `given!` definition: ::METHOD(MATCHER, ...) THEN REPEAT; ..."); } 149 | } 150 | 151 | 152 | #[cfg(test)] 153 | mod test { 154 | use galvanic_assert::*; 155 | use galvanic_assert::matchers::*; 156 | 157 | mod parsers { 158 | use super::*; 159 | use super::super::*; 160 | 161 | #[test] 162 | fn should_parse_bind() { 163 | let field = parse_bind("bind x: i32 = 1 + 2").expect(""); 164 | 165 | assert_that!(&field.name, eq(syn::Ident::from("x"))); 166 | assert_that!(&field.ty, is_variant!(syn::Ty::Path)); 167 | assert_that!(&field.initializer.node, is_variant!(syn::ExprKind::Binary)); 168 | } 169 | 170 | #[test] 171 | fn should_parse_given_return_from_call() { 172 | let stmt = &parse_given("::foo() then_return_from || { 2 } always").expect("")[0]; 173 | 174 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 175 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 176 | // assert_that!(args.is_empty(), otherwise "some arguments are detected"); 177 | assert_that!(&stmt.return_stmt, eq(Return::FromCall(syn::parse::expr("|| { 2 }").expect("")))); 178 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 179 | } 180 | 181 | #[test] 182 | fn should_parse_given_with_universal_function_call_syntax() { 183 | let stmt = &parse_given("::foo() then_return_from || { 2 } always").expect("")[0]; 184 | 185 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 186 | assert_that!(&stmt.ufc_trait, eq(syn::parse::path("MyTrait").expect("Could not parse expected type"))); 187 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 188 | // assert_that!(args.is_empty(), otherwise "some arguments are detected"); 189 | assert_that!(&stmt.return_stmt, eq(Return::FromCall(syn::parse::expr("|| { 2 }").expect("")))); 190 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 191 | } 192 | 193 | #[test] 194 | fn should_parse_given_spy_on_object() { 195 | let stmt = &parse_given("::foo() then_spy_on_object always").expect("")[0]; 196 | 197 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 198 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 199 | // assert_that!(args.is_empty(), otherwise "some arguments are detected"); 200 | assert_that!(&stmt.return_stmt, eq(Return::FromSpy)); 201 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 202 | } 203 | 204 | #[test] 205 | fn should_parse_given_panic() { 206 | let stmt = &parse_given("::foo() then_panic always").expect("")[0]; 207 | 208 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 209 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 210 | // assert_that!(args.is_empty(), otherwise "some arguments are detected"); 211 | assert_that!(&stmt.return_stmt, eq(Return::Panic)); 212 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 213 | } 214 | 215 | #[test] 216 | fn should_parse_given_times() { 217 | let stmt = &parse_given("::foo() then_return 1 times 2").expect("")[0]; 218 | 219 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 220 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 221 | // assert_that!(args.is_empty(), otherwise "some arguments are detected"); 222 | assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect("")))); 223 | assert_that!(&stmt.repeat, eq(GivenRepeat::Times(syn::parse::expr("2").expect("")))); 224 | } 225 | 226 | #[test] 227 | fn should_parse_given_always() { 228 | let stmt = &parse_given("::foo() then_return 1 always").expect("")[0]; 229 | 230 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 231 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 232 | // assert_that!(args.is_empty(), otherwise "some arguments are detected"); 233 | assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect("")))); 234 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 235 | } 236 | 237 | #[test] 238 | fn should_parse_given_args() { 239 | let stmt = &parse_given("::foo(2, 4) then_return 1 always").expect("")[0]; 240 | 241 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 242 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 243 | // assert_that!(&args, collection::contains_in_order(vec![syn::parse::expr("2").expect(""), syn::parse::expr("4").expect("")])); 244 | assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect("")))); 245 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 246 | } 247 | 248 | #[test] 249 | fn should_parse_given_matcher() { 250 | let stmt = &parse_given("::foo |a,b| true then_return 1 always").expect("")[0]; 251 | 252 | assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock"))); 253 | assert_that!(&stmt.method, eq(syn::Ident::from("foo"))); 254 | // assert_that!(&args, collection::contains_in_order(vec![syn::parse::expr("2").expect(""), syn::parse::expr("4").expect("")])); 255 | assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect("")))); 256 | assert_that!(&stmt.repeat, eq(GivenRepeat::Always)); 257 | } 258 | 259 | #[test] 260 | fn should_parse_givens() { 261 | let (binds, givens) = parse_givens("given! { ::foo() then_return 1 always; }").expect(""); 262 | 263 | assert_that!(&binds.len(), eq(0)); 264 | assert_that!(&givens.len(), eq(1)); 265 | } 266 | 267 | #[test] 268 | fn should_parse_givens_with_block() { 269 | let (binds, givens) = parse_givens("given! { ::foo() then_return 1 always; ::{ foo() then_return 1 always; foo() then_return 1 always; }; }").expect(""); 270 | 271 | assert_that!(&binds.len(), eq(0)); 272 | assert_that!(&givens.len(), eq(3)); 273 | } 274 | 275 | #[test] 276 | fn should_parse_givens_with_bind() { 277 | let (binds, givens) = parse_givens("given! { bind x: i32 = 1; bind x: f32 = 2.0; ::foo() then_return 1 always; }").expect(""); 278 | parse_givens("given ! { < x as TestTrait < i32 , f64 >>::func ( eq ( 2 ) , eq ( 2.2 ) ) then_return 12 always ; }").expect(""); 279 | 280 | assert_that!(&binds.len(), eq(2)); 281 | assert_that!(&givens.len(), eq(1)); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | #![recursion_limit = "128"] 17 | 18 | #[macro_use] mod acquire; 19 | mod new_mock; 20 | mod given; 21 | mod expect; 22 | mod generate; 23 | mod data; 24 | 25 | extern crate proc_macro; 26 | #[macro_use] extern crate lazy_static; 27 | 28 | extern crate syn; 29 | #[macro_use] extern crate synom; 30 | #[macro_use] extern crate quote; 31 | 32 | #[cfg(test)]#[macro_use] 33 | extern crate galvanic_assert; 34 | 35 | use proc_macro::TokenStream; 36 | 37 | use new_mock::handle_new_mock; 38 | use given::handle_given; 39 | use expect::handle_expect_interactions; 40 | use generate::handle_generate_mocks; 41 | use data::*; 42 | 43 | use std::env; 44 | use std::fs::File; 45 | use std::io::Write; 46 | use std::path::Path; 47 | 48 | 49 | enum MockedTraitLocation { 50 | TraitDef(syn::Path), 51 | Referred(syn::Path) 52 | } 53 | 54 | named!(parse_trait_path -> MockedTraitLocation, 55 | delimited!( 56 | punct!("("), 57 | do_parse!( 58 | external: option!(alt!(keyword!("intern") | keyword!("extern"))) >> path: call!(syn::parse::path) >> 59 | (match external { 60 | Some(..) => MockedTraitLocation::Referred(path), 61 | None => MockedTraitLocation::TraitDef(path) 62 | }) 63 | ), 64 | punct!(")") 65 | ) 66 | ); 67 | 68 | #[proc_macro_attribute] 69 | pub fn mockable(args: TokenStream, input: TokenStream) -> TokenStream { 70 | let s = input.to_string(); 71 | let trait_item = syn::parse_item(&s).expect("Expecting a trait definition."); 72 | 73 | let args_str = &args.to_string(); 74 | 75 | match trait_item.node { 76 | syn::ItemKind::Trait(safety, generics, bounds, items) => { 77 | let mut mockable_traits = acquire!(MOCKABLE_TRAITS); 78 | 79 | if args_str.is_empty() { 80 | mockable_traits.insert(trait_item.ident.clone().into(), TraitInfo::new(safety, generics, bounds, items)); 81 | return input; 82 | } 83 | 84 | let trait_location = parse_trait_path(args_str) 85 | .expect(concat!("#[mockable(..)] requires the absolute path of the trait's module.", 86 | "It must be preceded with `extern`/`intern` if the trait is defined in another crate/module")); 87 | match trait_location { 88 | MockedTraitLocation::TraitDef(mut trait_path) => { 89 | trait_path.segments.push(trait_item.ident.clone().into()); 90 | mockable_traits.insert(trait_path, TraitInfo::new(safety, generics, bounds, items)); 91 | input 92 | }, 93 | MockedTraitLocation::Referred(mut trait_path) => { 94 | trait_path.segments.push(trait_item.ident.clone().into()); 95 | mockable_traits.insert(trait_path, TraitInfo::new(safety, generics, bounds, items)); 96 | "".parse().unwrap() 97 | } 98 | } 99 | }, 100 | _ => panic!("Expecting a trait definition.") 101 | } 102 | } 103 | 104 | 105 | #[proc_macro_attribute] 106 | pub fn use_mocks(_: TokenStream, input: TokenStream) -> TokenStream { 107 | use MacroInvocationPos::*; 108 | 109 | // to parse the macros related to mock ussage the function is converted to string form 110 | let mut reassembled = String::new(); 111 | let parsed = syn::parse_item(&input.to_string()).unwrap(); 112 | let mut remainder = quote!(#parsed).to_string(); 113 | 114 | // parse one macro a time then search for the next macro in the remaining string 115 | let mut absolute_pos = 0; 116 | while !remainder.is_empty() { 117 | 118 | match find_next_mock_macro_invocation(&remainder) { 119 | None => { 120 | reassembled.push_str(&remainder); 121 | remainder = String::new(); 122 | }, 123 | Some(invocation) => { 124 | let (left, new_absolute_pos, right) = match invocation { 125 | NewMock(pos) => handle_macro(&remainder, pos, absolute_pos, handle_new_mock), 126 | Given(pos) => handle_macro(&remainder, pos, absolute_pos, handle_given), 127 | ExpectInteractions(pos) => handle_macro(&remainder, pos, absolute_pos, handle_expect_interactions), 128 | }; 129 | 130 | absolute_pos = new_absolute_pos; 131 | reassembled.push_str(&left); 132 | remainder = right; 133 | } 134 | } 135 | } 136 | 137 | // once all macro invocations have been removed from the string (and replaced with the actual mock code) it can be parsed back into a function item 138 | let mut mock_using_item = syn::parse_item(&reassembled).expect("Reassembled function whi"); 139 | mock_using_item.vis = syn::Visibility::Public; 140 | 141 | let item_ident = &mock_using_item.ident; 142 | let item_vis = &mock_using_item.vis; 143 | let mod_fn = syn::Ident::from(format!("mod_{}", item_ident)); 144 | 145 | 146 | if let syn::ItemKind::Mod(Some(ref mut mod_items)) = mock_using_item.node { 147 | insert_use_generated_mocks_into_modules(mod_items); 148 | } 149 | 150 | let mocks = handle_generate_mocks(); 151 | 152 | let generated_mock = (quote! { 153 | #[allow(unused_imports)] 154 | #item_vis use self::#mod_fn::#item_ident; 155 | mod #mod_fn { 156 | #![allow(dead_code)] 157 | #![allow(unused_imports)] 158 | #![allow(unused_variables)] 159 | use super::*; 160 | 161 | #mock_using_item 162 | 163 | pub(in self) mod mock { 164 | use std; 165 | use super::*; 166 | 167 | #(#mocks)* 168 | } 169 | } 170 | }).to_string(); 171 | 172 | debug(&item_ident, &generated_mock); 173 | generated_mock.parse().unwrap() 174 | } 175 | 176 | fn insert_use_generated_mocks_into_modules(mod_items: &mut Vec) { 177 | for item in mod_items.iter_mut() { 178 | if let syn::ItemKind::Mod(Some(ref mut sub_mod_items)) = item.node { 179 | insert_use_generated_mocks_into_modules(sub_mod_items); 180 | } 181 | } 182 | mod_items.push(syn::parse_item(quote!(pub use super::*;).as_str()).unwrap()); 183 | } 184 | 185 | fn debug(item_ident: &syn::Ident, generated_mock: &str) { 186 | if let Some((_, path)) = env::vars().find(|&(ref key, _)| key == "GA_WRITE_MOCK") { 187 | if path.is_empty() { 188 | println!("{}", generated_mock); 189 | } else { 190 | let success = File::create(Path::new(&path).join(&(item_ident.to_string()))) 191 | .and_then(|mut f| f.write_all(generated_mock.as_bytes())); 192 | if let Err(err) = success { 193 | eprintln!("Unable to write generated mock to file '{}' because: {}", path, err); 194 | } 195 | } 196 | } 197 | } 198 | 199 | fn has_balanced_quotes(source: &str) -> bool { 200 | let mut count = 0; 201 | let mut skip = false; 202 | for c in source.chars() { 203 | if skip { 204 | skip = false; 205 | continue; 206 | } 207 | 208 | if c == '\\' { 209 | skip = true; 210 | } else if c == '\"' { 211 | count += 1; 212 | } 213 | //TODO handle raw strings 214 | } 215 | count % 2 == 0 216 | } 217 | 218 | /// Stores position of a macro invocation with the variant naming the macro. 219 | enum MacroInvocationPos { 220 | NewMock(usize), 221 | Given(usize), 222 | ExpectInteractions(usize), 223 | } 224 | 225 | /// Find the next galvanic-mock macro invocation in the source string. 226 | /// 227 | /// Looks for `new_mock!``, `given!`, `expect_interactions!`, and `then_verify_interactions!`. 228 | /// The `source` string must have been reassembled from a `TokenTree`. 229 | /// The `source` string is expected to start in a code context, i.e., not inside 230 | /// a string. 231 | fn find_next_mock_macro_invocation(source: &str) -> Option { 232 | use MacroInvocationPos::*; 233 | // there must be a space between the macro name and the ! as the ! is a separate token in the tree 234 | let macro_names = ["new_mock !", "given !", "expect_interactions !"]; 235 | // not efficient but does the job 236 | macro_names.into_iter() 237 | .filter_map(|&mac| { 238 | source.find(mac).and_then(|pos| { 239 | if has_balanced_quotes(&source[.. pos]) { 240 | Some((pos, mac)) 241 | } else { None } 242 | }) 243 | }) 244 | .min_by_key(|&(pos, _)| pos) 245 | .and_then(|(pos, mac)| Some(match mac { 246 | "new_mock !" => NewMock(pos), 247 | "given !" => Given(pos), 248 | "expect_interactions !" => ExpectInteractions(pos), 249 | _ => panic!("Unreachable. No variant for macro name: {}", mac) 250 | })) 251 | } 252 | 253 | fn handle_macro(source: &str, mac_pos_relative_to_source: usize, absolute_pos_of_source: usize, handler: F) -> (String, usize, String) 254 | where F: Fn(&str, usize) -> (String, String) { 255 | let absolute_pos_of_mac = absolute_pos_of_source + mac_pos_relative_to_source; 256 | 257 | let (left_of_mac, right_with_mac) = source.split_at(mac_pos_relative_to_source); 258 | let (mut generated_source, unhandled_source) = handler(right_with_mac, absolute_pos_of_mac); 259 | generated_source.push_str(&unhandled_source); 260 | 261 | (left_of_mac.to_string(), absolute_pos_of_mac, generated_source) 262 | } 263 | 264 | 265 | #[cfg(test)] 266 | mod test_has_balanced_quotes { 267 | use super::*; 268 | 269 | #[test] 270 | fn should_have_balanced_quotes_if_none_exist() { 271 | let x = "df df df"; 272 | assert!(has_balanced_quotes(x)); 273 | } 274 | 275 | #[test] 276 | fn should_have_balanced_quotes_if_single_pair() { 277 | let x = "df \"df\" df"; 278 | assert!(has_balanced_quotes(x)); 279 | } 280 | 281 | #[test] 282 | fn should_have_balanced_quotes_if_single_pair_with_escapes() { 283 | let x = "df \"d\\\"f\" df"; 284 | assert!(has_balanced_quotes(x)); 285 | } 286 | 287 | #[test] 288 | fn should_have_balanced_quotes_if_multiple_pairs() { 289 | let x = "df \"df\" \"df\" df"; 290 | assert!(has_balanced_quotes(x)); 291 | } 292 | 293 | #[test] 294 | fn should_not_have_balanced_quotes_if_single() { 295 | let x = "df \"df df"; 296 | assert!(!has_balanced_quotes(x)); 297 | } 298 | 299 | #[test] 300 | fn should_not_have_balanced_quotes_if_escaped_pair() { 301 | let x = "df \"d\\\" df"; 302 | assert!(!has_balanced_quotes(x)); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/new_mock.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | use syn; 16 | use syn::parse::IResult; 17 | use data::*; 18 | 19 | named!(comma_separated_types -> Vec, 20 | separated_nonempty_list!(punct!(","), syn::parse::path) 21 | ); 22 | 23 | named!(outer_attr -> syn::Attribute, 24 | do_parse!( 25 | punct!("#") >> punct!("[") >> 26 | content: take_until!("]") >> 27 | punct!("]") >> 28 | (syn::parse_outer_attr(&format!("#[{}]", content)).unwrap()) 29 | ) 30 | ); 31 | 32 | named!(parse_new_mock -> RequestedMock, 33 | terminated!( 34 | do_parse!(keyword!("new_mock") >> punct!("!") >> punct!("(") >> 35 | traits: call!(comma_separated_types) >> 36 | attributes: many0!(outer_attr) >> 37 | maybe_type_name: option!(preceded!(keyword!("for"), syn::parse::ident)) >> 38 | punct!(")") >> 39 | (RequestedMock { traits, attributes, maybe_type_name }) 40 | ), punct!(";") 41 | ) 42 | ); 43 | 44 | pub fn handle_new_mock(source: &str, absolute_position: usize) -> (String, String) { 45 | if let IResult::Done(remainder, mut requested_mock) = parse_new_mock(source) { 46 | let mut requested_mocks = acquire!(REQUESTED_MOCKS); 47 | 48 | if requested_mock.maybe_type_name.is_none() { 49 | requested_mock.maybe_type_name = Some(syn::Ident::from(format!("Mock{}", absolute_position))); 50 | } 51 | let mock_type_name = requested_mock.maybe_type_name.clone().unwrap(); 52 | requested_mocks.push(requested_mock); 53 | 54 | let assignment_stmt = quote! { mock::#mock_type_name::new(); }; 55 | return (assignment_stmt.to_string(), remainder.to_string()); 56 | } 57 | 58 | panic!(format!(concat!("Expecting a new_mock defintion of the form: new_mock!(paths::to::Traits, ... #[optional_attributes]...);\n", 59 | "\tGot: {}"), source)); 60 | } 61 | -------------------------------------------------------------------------------- /tests/expect_different_interactions.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32) -> i32; 23 | } 24 | 25 | mod test_times { 26 | use super::*; 27 | 28 | #[test] 29 | #[use_mocks] 30 | fn matching_expectation() { 31 | let mock = new_mock!(TestTrait); 32 | 33 | given! { 34 | ::func |_| true then_return 12 always; 35 | } 36 | 37 | expect_interactions! { 38 | ::func(|&a| a < 2) times(2); 39 | } 40 | 41 | mock.func(1); 42 | mock.func(1); 43 | mock.func(3); 44 | mock.func(3); 45 | 46 | mock.verify(); 47 | } 48 | 49 | #[test] 50 | #[should_panic] 51 | #[use_mocks] 52 | fn violating_expectation_too_few() { 53 | let mock = new_mock!(TestTrait); 54 | 55 | given! { 56 | ::func |_| true then_return 12 always; 57 | } 58 | 59 | expect_interactions! { 60 | ::func(|&a| a < 2) times(2); 61 | } 62 | 63 | mock.func(1); 64 | mock.func(3); 65 | mock.func(3); 66 | 67 | mock.verify(); 68 | } 69 | 70 | #[test] 71 | #[should_panic] 72 | #[use_mocks] 73 | fn violating_expectation_too_many() { 74 | let mock = new_mock!(TestTrait); 75 | 76 | given! { 77 | ::func |_| true then_return 12 always; 78 | } 79 | 80 | expect_interactions! { 81 | ::func(|&a| a < 2) times(2); 82 | } 83 | 84 | mock.func(1); 85 | mock.func(1); 86 | mock.func(1); 87 | mock.func(3); 88 | mock.func(3); 89 | 90 | mock.verify(); 91 | } 92 | } 93 | 94 | mod test_at_most { 95 | use super::*; 96 | 97 | #[test] 98 | #[use_mocks] 99 | fn matching_expectation_exact() { 100 | let mock = new_mock!(TestTrait); 101 | 102 | given! { 103 | ::func |_| true then_return 12 always; 104 | } 105 | 106 | expect_interactions! { 107 | ::func(|&a| a < 2) at_most 2; 108 | } 109 | 110 | mock.func(1); 111 | mock.func(1); 112 | mock.func(3); 113 | mock.func(3); 114 | 115 | mock.verify(); 116 | } 117 | 118 | #[test] 119 | #[use_mocks] 120 | fn matching_expectation_fewer() { 121 | let mock = new_mock!(TestTrait); 122 | 123 | given! { 124 | ::func |_| true then_return 12 always; 125 | } 126 | 127 | expect_interactions! { 128 | ::func(|&a| a < 2) at_most 2; 129 | } 130 | 131 | mock.func(1); 132 | mock.func(3); 133 | mock.func(3); 134 | 135 | mock.verify(); 136 | } 137 | 138 | #[test] 139 | #[should_panic] 140 | #[use_mocks] 141 | fn violating_expectation_too_many() { 142 | let mock = new_mock!(TestTrait); 143 | 144 | given! { 145 | ::func |_| true then_return 12 always; 146 | } 147 | 148 | expect_interactions! { 149 | ::func(|&a| a < 2) at_most 2; 150 | } 151 | 152 | mock.func(1); 153 | mock.func(1); 154 | mock.func(1); 155 | mock.func(3); 156 | mock.func(3); 157 | 158 | mock.verify(); 159 | } 160 | } 161 | 162 | mod test_at_least { 163 | use super::*; 164 | 165 | #[test] 166 | #[use_mocks] 167 | fn matching_expectation() { 168 | let mock = new_mock!(TestTrait); 169 | 170 | given! { 171 | ::func |_| true then_return 12 always; 172 | } 173 | 174 | expect_interactions! { 175 | ::func(|&a| a < 2) at_least 2; 176 | } 177 | 178 | mock.func(1); 179 | mock.func(1); 180 | mock.func(3); 181 | mock.func(3); 182 | 183 | mock.verify(); 184 | } 185 | 186 | #[test] 187 | #[should_panic] 188 | #[use_mocks] 189 | fn violating_expectation_too_few() { 190 | let mock = new_mock!(TestTrait); 191 | 192 | given! { 193 | ::func |_| true then_return 12 always; 194 | } 195 | 196 | expect_interactions! { 197 | ::func(|&a| a < 2) at_least 2; 198 | } 199 | 200 | mock.func(1); 201 | mock.func(3); 202 | mock.func(3); 203 | 204 | mock.verify(); 205 | } 206 | 207 | #[test] 208 | #[use_mocks] 209 | fn matching_expectation_more() { 210 | let mock = new_mock!(TestTrait); 211 | 212 | given! { 213 | ::func |_| true then_return 12 always; 214 | } 215 | 216 | expect_interactions! { 217 | ::func(|&a| a < 2) at_least 2; 218 | } 219 | 220 | mock.func(1); 221 | mock.func(1); 222 | mock.func(1); 223 | mock.func(3); 224 | mock.func(3); 225 | 226 | mock.verify(); 227 | } 228 | } 229 | 230 | mod test_between { 231 | use super::*; 232 | 233 | #[test] 234 | #[use_mocks] 235 | fn matching_expectation_exact_lower_bound() { 236 | let mock = new_mock!(TestTrait); 237 | 238 | given! { 239 | ::func |_| true then_return 12 always; 240 | } 241 | 242 | expect_interactions! { 243 | ::func(|&a| a < 2) between 2,4; 244 | } 245 | 246 | mock.func(1); 247 | mock.func(1); 248 | mock.func(3); 249 | mock.func(3); 250 | 251 | mock.verify(); 252 | } 253 | 254 | #[test] 255 | #[use_mocks] 256 | fn matching_expectation_between_bounds() { 257 | let mock = new_mock!(TestTrait); 258 | 259 | given! { 260 | ::func |_| true then_return 12 always; 261 | } 262 | 263 | expect_interactions! { 264 | ::func(|&a| a < 2) between 2,4; 265 | } 266 | 267 | mock.func(1); 268 | mock.func(1); 269 | mock.func(3); 270 | mock.func(3); 271 | mock.func(1); 272 | 273 | mock.verify(); 274 | } 275 | 276 | #[test] 277 | #[use_mocks] 278 | fn matching_expectation_exact_upper_bound() { 279 | let mock = new_mock!(TestTrait); 280 | 281 | given! { 282 | ::func |_| true then_return 12 always; 283 | } 284 | 285 | expect_interactions! { 286 | ::func(|&a| a < 2) between 2,4; 287 | } 288 | 289 | mock.func(1); 290 | mock.func(1); 291 | mock.func(3); 292 | mock.func(3); 293 | mock.func(1); 294 | mock.func(1); 295 | 296 | mock.verify(); 297 | } 298 | 299 | #[test] 300 | #[should_panic] 301 | #[use_mocks] 302 | fn violating_expectation_too_few() { 303 | let mock = new_mock!(TestTrait); 304 | 305 | given! { 306 | ::func |_| true then_return 12 always; 307 | } 308 | 309 | expect_interactions! { 310 | ::func(|&a| a < 2) times(2); 311 | } 312 | 313 | mock.func(1); 314 | mock.func(3); 315 | mock.func(3); 316 | 317 | mock.verify(); 318 | } 319 | 320 | #[test] 321 | #[should_panic] 322 | #[use_mocks] 323 | fn violating_expectation_too_many() { 324 | let mock = new_mock!(TestTrait); 325 | 326 | given! { 327 | ::func |_| true then_return 12 always; 328 | } 329 | 330 | expect_interactions! { 331 | ::func(|&a| a < 2) times(2); 332 | } 333 | 334 | mock.func(1); 335 | mock.func(1); 336 | mock.func(1); 337 | mock.func(1); 338 | mock.func(1); 339 | mock.func(3); 340 | mock.func(3); 341 | 342 | mock.verify(); 343 | } 344 | } 345 | 346 | mod test_never { 347 | use super::*; 348 | 349 | #[test] 350 | #[use_mocks] 351 | fn matching_expectation() { 352 | let mock = new_mock!(TestTrait); 353 | 354 | given! { 355 | ::func |_| true then_return 12 always; 356 | } 357 | 358 | expect_interactions! { 359 | ::func(|&a| a < 2) never; 360 | } 361 | 362 | mock.func(3); 363 | mock.func(3); 364 | 365 | mock.verify(); 366 | } 367 | 368 | #[test] 369 | #[should_panic] 370 | #[use_mocks] 371 | fn violating_expectation() { 372 | let mock = new_mock!(TestTrait); 373 | 374 | given! { 375 | ::func |_| true then_return 12 always; 376 | } 377 | 378 | expect_interactions! { 379 | ::func(|&a| a < 2) never; 380 | } 381 | 382 | mock.func(1); 383 | mock.func(3); 384 | mock.func(3); 385 | 386 | mock.verify(); 387 | } 388 | } 389 | 390 | 391 | mod multiple_traits { 392 | use super::*; 393 | 394 | #[mockable(::multiple_traits)] 395 | trait OtherTrait { 396 | fn other_func(&self) -> i32; 397 | } 398 | 399 | #[test] 400 | #[use_mocks] 401 | fn matching_expectation() { 402 | let mock = new_mock!(TestTrait, ::multiple_traits::OtherTrait); 403 | 404 | given! { 405 | ::func |_| true then_return 12 always; 406 | ::other_func |_| true then_return 24 always; 407 | } 408 | 409 | expect_interactions! { 410 | ::func(|&a| a < 2) never; 411 | ::other_func |_| true never; 412 | } 413 | 414 | mock.func(3); 415 | mock.func(3); 416 | 417 | mock.verify(); 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /tests/expect_method_without_args.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self) -> usize; 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func() then_return 42usize always; 32 | } 33 | 34 | expect_interactions! { 35 | ::func() times 1; 36 | } 37 | 38 | mock.func(); 39 | } 40 | -------------------------------------------------------------------------------- /tests/expect_void_method.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32); 23 | } 24 | 25 | mod test_times { 26 | use super::*; 27 | 28 | #[test] 29 | #[use_mocks] 30 | fn matching_expectation() { 31 | let mock = new_mock!(TestTrait); 32 | 33 | given! { 34 | ::func(|_| true) then_return () always; 35 | } 36 | 37 | expect_interactions! { 38 | ::func(|&a| a < 2) times(1); 39 | } 40 | 41 | mock.func(1); 42 | 43 | mock.verify(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/galvanic_assert_integration.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![cfg(feature = "galvanic_assert_integration")] 16 | #![feature(proc_macro)] 17 | 18 | extern crate galvanic_mock; 19 | #[allow(unused_imports)] use galvanic_mock::{mockable, use_mocks}; 20 | extern crate galvanic_assert; 21 | #[allow(unused_imports)] use galvanic_assert::matchers::*; 22 | 23 | #[mockable] 24 | trait TestTrait { 25 | fn func(&self, x: i32, y: f64) -> i32; 26 | } 27 | 28 | #[test] 29 | #[use_mocks] 30 | fn test_per_argument_matcher_with_galvanic_assert_matchers() { 31 | let mock = new_mock!(TestTrait); 32 | 33 | given! { 34 | ::func(lt(2), gt(3.3)) then_return 12 always; 35 | } 36 | 37 | assert_eq!(mock.func(1, 4.4), 12); 38 | } 39 | 40 | #[test] 41 | #[use_mocks] 42 | fn test_explicit_matcher_with_galvanic_assert_matcher() { 43 | let mock = new_mock!(TestTrait); 44 | 45 | given! { 46 | ::func eq((2, 3.3)) then_return 12 always; 47 | } 48 | 49 | assert_eq!(mock.func(2, 3.3), 12); 50 | } 51 | -------------------------------------------------------------------------------- /tests/given_block_missing_for_method.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32) -> i32; 23 | } 24 | 25 | #[test] 26 | #[should_panic] 27 | #[use_mocks] 28 | fn test_per_argument_matcher() { 29 | let mock = new_mock!(TestTrait); 30 | mock.func(1); 31 | } 32 | -------------------------------------------------------------------------------- /tests/given_different_repeats.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32) -> i32; 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test_times_within_bound() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func |_| true then_return 12 times(2); 32 | } 33 | 34 | assert_eq!(mock.func(1), 12); 35 | assert_eq!(mock.func(2), 12); 36 | } 37 | 38 | #[test] 39 | #[should_panic] 40 | #[use_mocks] 41 | fn test_times_out_of_bound() { 42 | let mock = new_mock!(TestTrait); 43 | 44 | given! { 45 | ::func |_| true then_return 12 times(2); 46 | } 47 | 48 | assert_eq!(mock.func(1), 12); 49 | assert_eq!(mock.func(2), 12); 50 | assert_eq!(mock.func(3), 12); 51 | } 52 | 53 | #[test] 54 | #[use_mocks] 55 | fn test_times_in_sequence() { 56 | let mock = new_mock!(TestTrait); 57 | 58 | given! { 59 | ::{ 60 | func |_| true then_return 12 times(1); 61 | func |_| true then_return 24 times(1); 62 | } 63 | } 64 | 65 | assert_eq!(mock.func(1), 12); 66 | assert_eq!(mock.func(2), 24); 67 | } 68 | 69 | #[test] 70 | #[use_mocks] 71 | fn test_multiple_given_blocks_with_reset_of_given_behaviours() { 72 | let mut mock = new_mock!(TestTrait); 73 | 74 | given! { 75 | ::func |_| true then_return 12 always; 76 | } 77 | assert_eq!(mock.func(1), 12); 78 | 79 | mock.reset_given_behaviours(); 80 | 81 | given! { 82 | ::func |_| true then_return 24 always; 83 | } 84 | assert_eq!(mock.func(2), 24); 85 | } 86 | -------------------------------------------------------------------------------- /tests/given_different_returns.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32) -> i32; 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test_then_return_from() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func |_| true then_return_from |&(a,)| a*2 always; 32 | } 33 | 34 | assert_eq!(mock.func(1), 2); 35 | assert_eq!(mock.func(2), 4); 36 | } 37 | 38 | 39 | #[test] 40 | #[should_panic] 41 | #[use_mocks] 42 | fn test_then_panic() { 43 | let mock = new_mock!(TestTrait); 44 | 45 | given! { 46 | ::func |&(a,)| a < 2 then_panic always; 47 | } 48 | 49 | mock.func(1); 50 | } 51 | -------------------------------------------------------------------------------- /tests/given_method_with_multiple_args.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32, y: f64) -> i32; 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test_per_argument_matcher() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func(|&a| a < 2, |&b| b > 3.3) then_return 12 always; 32 | ::func(|&a| a >= 2, |&b| b > 3.3) then_return 24 always; 33 | } 34 | 35 | assert_eq!(mock.func(1, 4.4), 12); 36 | assert_eq!(mock.func(1, 4.4), 12); 37 | assert_eq!(mock.func(3, 4.4), 24); 38 | assert_eq!(mock.func(3, 4.4), 24); 39 | } 40 | 41 | #[test] 42 | #[use_mocks] 43 | fn test_explicit_matcher() { 44 | let mock = new_mock!(TestTrait); 45 | 46 | given! { 47 | ::func |&(a,b)| a < 2 then_return 12 always; 48 | ::func |&(a,b)| a >= 2 then_return 24 always; 49 | } 50 | 51 | assert_eq!(mock.func(1, 4.4), 12); 52 | assert_eq!(mock.func(1, 4.4), 12); 53 | assert_eq!(mock.func(3, 4.4), 24); 54 | assert_eq!(mock.func(3, 4.4), 24); 55 | } 56 | -------------------------------------------------------------------------------- /tests/given_method_with_single_arg.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32) -> i32; 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test_per_argument_matcher() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func(|&a| a < 2) then_return 12 always; 32 | ::func(|&a| a >= 2) then_return 24 always; 33 | } 34 | 35 | assert_eq!(mock.func(1), 12); 36 | assert_eq!(mock.func(1), 12); 37 | assert_eq!(mock.func(3), 24); 38 | assert_eq!(mock.func(3), 24); 39 | } 40 | 41 | #[test] 42 | #[use_mocks] 43 | fn test_explicit_matcher() { 44 | let mock = new_mock!(TestTrait); 45 | 46 | given! { 47 | ::func |&(a,)| a < 2 then_return 12 always; 48 | ::func |&(a,)| a >= 2 then_return 24 always; 49 | } 50 | 51 | assert_eq!(mock.func(1), 12); 52 | assert_eq!(mock.func(1), 12); 53 | assert_eq!(mock.func(3), 24); 54 | assert_eq!(mock.func(3), 24); 55 | } 56 | -------------------------------------------------------------------------------- /tests/given_method_without_args.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self) -> usize; 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func() then_return 42usize always; 32 | } 33 | 34 | assert_eq!(mock.func(), 42usize); 35 | } 36 | -------------------------------------------------------------------------------- /tests/given_void_method_without_args.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self); 23 | } 24 | 25 | #[test] 26 | #[use_mocks] 27 | fn test() { 28 | let mock = new_mock!(TestTrait); 29 | 30 | given! { 31 | ::func() then_return () always; 32 | } 33 | 34 | mock.func(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/mock_empty_trait.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | 21 | #[mockable] 22 | trait EmptyTrait { } 23 | 24 | #[test]#[use_mocks] 25 | fn create_mock_with_empty_trait() { 26 | let mock = new_mock!(EmptyTrait); 27 | } 28 | -------------------------------------------------------------------------------- /tests/mock_generic_trait.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | pub trait TestTrait<'a, T,F> { 22 | type Assoc; 23 | fn func(&self, x: T, y: &F) -> i32; 24 | } 25 | 26 | #[test]#[use_mocks] 27 | fn test() { 28 | let x = new_mock!(TestTrait); 29 | 30 | given! { 31 | bind y: i32 = 12; 32 | >::func(|&a| a < 2, |&&b| b < 2.2) then_return 23 times(1); 33 | >::func(|&a| a < 4, |&&b| b < 2.2) then_return_from |_| bound.y * 2 always; 34 | } 35 | 36 | expect_interactions! { 37 | >::func(|&a| a < 2, |&&b| b < 2.2) at_least 1; 38 | >::func(|&a| a > 2, |&&b| true) between 2,3; 39 | } 40 | 41 | assert!(x.func(1, &1.1) == 23); 42 | assert!(x.func(3, &1.1) == 24); 43 | assert!(x.func(3, &1.1) == 24); 44 | 45 | x.verify(); 46 | } 47 | -------------------------------------------------------------------------------- /tests/mock_generic_trait_method.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | pub trait TestTrait { 22 | fn func(&self, x: T, y: T) -> i32; 23 | } 24 | 25 | #[test]#[use_mocks] 26 | fn test() { 27 | let x = new_mock!(TestTrait); 28 | 29 | given! { 30 | ::func |&(ref a, ref b)| a == b then_return 23 always; 31 | ::func |&(ref a, ref b)| true then_return 46 always; 32 | } 33 | 34 | expect_interactions! { 35 | ::func |&(ref a, ref b)| a == b times 2; 36 | ::func |&(ref a, ref b)| a > b times 2; 37 | ::func |&(ref a, ref b)| a < b never; 38 | } 39 | 40 | assert!(x.func(1, 1) == 23); 41 | assert!(x.func(3, 2) == 46); 42 | assert!(x.func(3, 3) == 23); 43 | assert!(x.func(3, 2) == 46); 44 | 45 | x.verify(); 46 | } 47 | -------------------------------------------------------------------------------- /tests/mock_referred_trait.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | mod sub1 { 21 | pub mod sub2 { 22 | pub trait EmptyTrait { } 23 | } 24 | } 25 | 26 | #[mockable(intern ::sub1::sub2)] 27 | trait EmptyTrait { } 28 | 29 | #[test]#[use_mocks] 30 | fn create_mock_for_referred_trait() { 31 | let mock = new_mock!(::sub1::sub2::EmptyTrait); 32 | } 33 | -------------------------------------------------------------------------------- /tests/mock_trait_and_apply_attributes.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | 21 | #[mockable] 22 | trait EmptyTrait { } 23 | 24 | #[test]#[use_mocks] 25 | fn create_mock_with_attributes() { 26 | let mock = new_mock!(EmptyTrait #[allow(dead_code)]#[allow(unused_variables)]); 27 | } 28 | -------------------------------------------------------------------------------- /tests/mock_trait_in_submodules.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | 19 | mod sub1 { 20 | pub mod sub2 { 21 | use galvanic_mock::mockable; 22 | // this macro must be expanded first 23 | #[mockable(::sub1::sub2)] 24 | pub trait EmptyTrait { } 25 | } 26 | } 27 | 28 | 29 | mod test1 { 30 | use galvanic_mock::use_mocks; 31 | #[test]#[use_mocks] 32 | fn create_mock_from_other_submodule() { 33 | let mock = new_mock!(::sub1::sub2::EmptyTrait); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/mock_with_explicit_type_name.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | 21 | #[mockable] 22 | trait TestTrait { 23 | fn func(&self, x: i32) -> i32; 24 | } 25 | 26 | #[use_mocks] 27 | mod test_module1 { 28 | use super::TestTrait; 29 | 30 | fn create_mock() -> mock::MyMock { 31 | let mock = new_mock!(TestTrait for MyMock); 32 | 33 | given! { 34 | ::func |_| true then_return 12 times 1; 35 | } 36 | 37 | mock 38 | } 39 | 40 | #[test] 41 | fn use_of_mock_factory_1() { 42 | let mock = create_mock(); 43 | 44 | given! { 45 | ::func |_| true then_return 1 always; 46 | } 47 | 48 | assert_eq!(mock.func(1), 12); 49 | assert_eq!(mock.func(1), 1); 50 | } 51 | 52 | #[test] 53 | fn use_of_mock_factory_2() { 54 | let mock = create_mock(); 55 | 56 | given! { 57 | ::func |_| true then_return 2 always; 58 | } 59 | 60 | assert_eq!(mock.func(1), 12); 61 | assert_eq!(mock.func(1), 2); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/use_mocks_on_module.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | 21 | #[mockable] 22 | trait TestTrait { 23 | fn func(&self, x: i32) -> i32; 24 | } 25 | 26 | #[use_mocks] 27 | mod test_module1 { 28 | use super::TestTrait; 29 | 30 | #[test] 31 | fn test_in_annotated_module() { 32 | let mock = new_mock!(TestTrait); 33 | 34 | given! { 35 | ::func |_| true then_return 12 always; 36 | } 37 | 38 | assert_eq!(mock.func(1), 12); 39 | } 40 | } 41 | 42 | #[use_mocks] 43 | mod test_module2 { 44 | use super::TestTrait; 45 | 46 | mod sub1 { 47 | mod sub2 { 48 | #[test] 49 | fn test_in_submodules_of_annotated_module() { 50 | let mock = new_mock!(TestTrait); 51 | 52 | given! { 53 | ::func |_| true then_return 12 always; 54 | } 55 | 56 | assert_eq!(mock.func(1), 12); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/verify_on_drop.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Christopher Bacher 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #![feature(proc_macro)] 16 | extern crate galvanic_mock; 17 | extern crate galvanic_assert; 18 | use galvanic_mock::{mockable, use_mocks}; 19 | 20 | #[mockable] 21 | trait TestTrait { 22 | fn func(&self, x: i32) -> i32; 23 | } 24 | 25 | #[test] 26 | #[should_panic] 27 | #[use_mocks] 28 | fn verify_on_drop() { 29 | let mock = new_mock!(TestTrait); 30 | 31 | given! { 32 | ::func |_| true then_return 12 always; 33 | } 34 | 35 | expect_interactions! { 36 | ::func(|&a| a < 2) times(2); 37 | } 38 | 39 | mock.func(1); 40 | } 41 | 42 | #[test] 43 | #[use_mocks] 44 | fn disable_verify_on_drop() { 45 | let mut mock = new_mock!(TestTrait); 46 | 47 | given! { 48 | ::func |_| true then_return 12 always; 49 | } 50 | 51 | expect_interactions! { 52 | ::func(|&a| a < 2) times(2); 53 | } 54 | 55 | mock.should_verify_on_drop(false); 56 | mock.func(1); 57 | } 58 | --------------------------------------------------------------------------------