1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
//! Test framework for actors //! //! # Table of Contents //! 1. [Introduction](#introduction) //! 2. [TestActorSystem](#testactorsystem) //! 3. [TestActorRef](#testactorref) //! 4. [TestProbe](#testprobe) //! 5. [Examples](#examples) //! //! # Introduction //! //! Amazing opportunity of the untyped actors consists in the feature, that actor may by tested in //! fully isolated test environment. All outside environment of the actor will be mocked, but he //! itself will be think, that it work in the real world system, and never will known about that //! all they environment is emulated. In order to achieve this effect, testkit use few elements. //! Let's look on they! //! //! # TestLocalActorSystem //! //! This is a special test realization of the actor system (actor system itself is abstract interface, //! and may have any realizations). She fully duplicate functional of the regular actor system, but //! add to her various functions for needs of testing. She may be created as regular actor system. //! Only difference consist in that this system, does not need run explicitly. She is run itself //! right after creation. //! //! ``` //! let system = TestLocalActorSystem::new(); //! ``` //! //! # TestLocalActorRef //! //! Special ActorRef which produced by the TestLocalActorSystem. Like the last, he is fully //! duplicate functions of the regular reference with appending an additional functions. //! //! ### Access to the actor's state //! //! TestLocalActorRef allow you get direct access to the internal actor object and see it's state. //! This state is immutable, but may be used for assertions about states change after processing //! some test actions. Link to the actor is accessed as "actor" attribute of the reference object //! and represents as Any type object. For use it, you need downcast they to the concrete type. //! Because it is strongly complex operation, testkit present special macro for this: //! //! ``` //! in_state! (target, SomeType, actor => { //! assert_eq!(actor.data, 599); //! }); //! ``` //! //! It this expression, 'target' is the link to the ActorRef, 'SomeType' is the actor's concrete //! type and the 'actor' is the casted variable for inner use. //! //! Important note! For use this feature, target actor must implement as_any method of the 'Actor' //! trait. Without satisfaction this requirement, type cast operation over actor object will be //! completed with panic! This is example of simplest implementation of this method: //! //! ``` //! fn as_any(self: &Self) -> &Any { self } //! ``` //! //! ### Substitution of created actors //! //! Method replace_actor_of do the next thing. He sets special flag in the actor system, which //! indicates, that when next actor_of will be called, instead of creating new actor, specified //! ActorRef will be return. This feature is used when tested actor must create some other //! actor, and you want to intercept this action and inject a test probe actor instead a real. //! After that, you will receive all message addressed to that actor and may respond to it. //! For example, it is possible to present the following situation. You send a message to a //! target actor. He handle that message, creates new other actor and send to him some other //! message. You may intercept this interaction in such code: //! //! ``` //! system.replace_actor_of(fiction.aref()); //! probe.send(&mut target, msg!(commands::SomeMessages { })); //! fiction.expect_msg(type_matcher!(other_actor_commands::SomeMessages)); //! ``` //! //! # TestProbe //! //! This is a heart part of actor's testing. TestProbe is an object, which constructed over inner //! actor and allows for external code, interact with it in a blocking manner. Probe may by created //! from the test actor system: //! //! ``` //! let probe = system.create_probe(Some("my_probe")); //! ``` //! //! After that, with him may be performed various operations. //! //! ### Send //! //! Sends specified message the specified actor on behalf of the probe actor. Target actor may //! respond on this message, and response will be received by the probe actor. //! //! ### Expectations //! //! The main feature of the Test Probe consists in than it may expect some messages and fail test //! if this messages does not satisfy for specified requirements. For this task, probe presents few //! method which names starts with 'expect' prefix. Each of this methods receive as arg one or more //! anonymous functions which is called 'matcher'. Matcher receive a message as argument and return //! a bool value witch determine result of matching test for a message. All expectors functions is //! blocks the called thread. After block, inner actor //! must receive a message/messages and pass them through early set matchers. If the inner actor //! receive a messages, expector function will be unlocked and decide what to do with results of //! matching - pass test or fail it. If the inner actor does not receive any messages in the //! specified timeout, expector function also will be unlocked, and test will be failed by timeout //! error. Default timeout is set to 3 seconds. This time may be tuned though set_timeout method. //! Now let's define list of existed expects. //! //! * expect_msg - Expects a single message. Returns an instance of an intercepted message. //! * expect_msg_any_of - Expects any message from the presented set. Returns an instance of an //! intercepted message. //! * expect_msg_all_off - Expects all message from the presented set. Order of the messages is not //! determined. Also target messages may to alternate with message does not from set. Returns a //! list of instances of an intercepted messages. //! * expect_no_msg - Expects that no one message will be received in the specified time interval. //! * expect_terminated - Expects that specified actor will be stopped. Before perform this //! expectations, probe must be watch target actor: //! //! ``` //! probe.watch(&target); //! ``` //! //! Now what with matchers. Matcher is a function with next signature: //! //! ```text //! type Matcher = Box<Fn(Message) -> bool + Send>; //! ``` //! //! Typical operations, which do a matchers, consists in check of message type through it's //! downcasting and pass some clarifying checks, such us more precise pattern matching. Example of //! typical matcher function is presented below. //! //! //! ``` //! Box::new(|v: Message| { //! if let Some(m) = v.get().downcast_ref::<logger::Log>() { //! if m.text.len() > 100 { //! match m.target { //! logger::LogTarget::StdOut => true, //! _ => false //! } //! } else { //! false //! } //! } else { //! false //! } //!}); //! ``` //! //! Of course this is a very dirty code and testkit presents few macros, in order to tests code looks //! like more clean. First such macro is 'matcher!'. It is the simple wrapper for hiding header //! details of the function. //! //! ``` //! matcher!(v => { //! if let Some(m) = v.get().downcast_ref::<logger::Log>() { //! if m.text.len() > 100 { //! match m.target { //! logger::LogTarget::StdOut => true, //! _ => false //! } //! } else { //! false //! } //! } else { //! false //! } //! }); //! ``` //! //! Second type, constructs a function, which checks a message for match to the specified type. //! //! ``` //! type_matcher!(logger::Log); //! ``` //! //! Third type, constructs a function, which checks a message for match to the specified simple //! pattern (without guards). //! //! ``` //! pat_matcher!(logger::Log => logger::Log { text: _, target: logger::LogTarget::StdOut }); //! ``` //! //! Fourth type, is most useful. It constructs a function, which checks a message for match to the //! specified type and after that apply to the casted message some user code with additional checks. //! This is useful when simple pattern match checks does not sufficient. For example you need call a //! method of the object which lives as field of the message. //! //! ``` //! extended_type_matcher!(logger::Log, v => { //! if v.get().text.len() > 100 { //! true //! } else { //! false //! } //! }); //! ``` //! //! Summarizing the paragraph, below present example of usage expector-functions with described //! matchers. //! //! ``` //! probe.expect_msg(type_matcher!(logger::Log)); //! //! // or //! //! probe.expect_msg_any_of( //! vec! [ //! pat_matcher!(logger::Log => logger::Log { text: _, target: logger::LogTarget::StdOut }), //! pat_matcher!(logger::Log => logger::Log { text: _, target: logger::LogTarget::File }) //! ] //! ); //! //! ``` //! //! ### Reply //! //! This function permits to you send a message to the sender of last received message. In other //! words, you may respond to the sender. This allow to you construct message passing chains with //! target actor. You send message to the target actor, receive response from it and if it is //! matched, reply to them with new message, and again wait a message and so on. //! //! ``` //! probe.send(target.clone(), msg!(some_actor::DoFirstAction { })); //! probe.expect_msg(type_matcher!(some_actor::OkForFirstAction)); //! probe.send(target.clone(), msg!(some_actor::DoSecondAction { })); //! probe.expect_msg(type_matcher!(some_actor::OkForSecondAction)); //! //! ``` //! //! ### DI testing //! //! Internal actor is a regular actor existed under standard ActorRef. This ActorRef may be //! extracted, cloned and passed as dependency to the tested actor. This operation is names as //! dependency injection testing. Injected test actor reference will be receive all message which //! will be addressed to emulated dependency. For see hiw it work, remember example with logger //! from the main doc. Logger actor receive few actors as dependency's, which realize concrete //! logic of writing logs. Logger after receive the Log message, determines log target and send //! command for write log, to the specified writer. All this logic may tested by fully transparent //! way: //! //! ``` //! let mut probe = system.create_probe(Some("probe")); //! let mut stdout_writer = system.create_probe(Some("stdout_writer")); //! let mut file_writer = system.create_probe(Some("file_writer")); //! let mut target = system.actor_of(logger::props(file_writer.aref(), stdout_writer.aref()), Some("logger")); //! //! probe.send(target.clone(), msg!(logger::Log { text: String::from("_"), target: logger::LogTarget::File })); //! file_writer.expect_msg(type_matcher!(file_writer::Write)); //! //! probe.send(target.clone(), msg!(logger::Log { text: String::from("_"), target: logger::LogTarget::StdOut })); //! stdout_writer.expect_msg(type_matcher!(stdout_writer::Write)); //! ``` //! //! ### Use with caution //! //! TestProbe must always consume received message and and here's why. Internally any TestProbe //! presented as probe struct and actor which interact with target. Receive method of the test actor //! is synchronized with TestProbe creator thread through conditional variable. When he receive //! a message, he locks on this variable and wait, while test thread does not permits process this //! message. And now see on the next situation. You test some target, send message to it but next //! do nothing with TestProbe. Target actor receives sent message and respond with some other. //! Test actor receives this message and locks on condvar. Attentions - he fully lock dispatcher thread //! for wait unlocking from test thread. But this will doesn't happen, because you does not call //! any expector of this probe. Oops! You do step to deadlock of the system dispatcher. A few more such tricks //! and testing will be nothing more, all threads of the system will be blocked. This situation //! eliminates in two way. First - you mast always to call some expector of TestProbe (for example //! - expect_no_msg). Second - you must pack separate test cases in blocks of code, because when //! TestProbe will be dropped, he automatically unlocks condvar. //! //! # Helper macros //! //! In the testkit exists few macros which simplify some aspects of testing. //! //! * cast! - Casts some Message to the specified type and call a user function with this value. Macro //! may be used in two modes. First as validator, when you getting access to the internal data of //! a message. Second as extractor of data from a messages, because he may return values from itself //! based on the data from a message. //! //! ``` //! // As validator //! cast!(msg, responses::MsgResponse, m => { //! assert_eq!(m.data, 99); //! }); //! //! // As extractor //! let data = cast!(msg, responses::MsgResponse, m => { //! m.data; //! }); //! ``` //! //! # Examples //! //! In the 'examples/testkit/bagsman.rs' presented all types of test actions which you may do with //! this testkit. Initially all tests in this file is passable, but in the commented blocks of code, //! defined various situations with tests which will be failed for some reasons. Uncomment it fow see //! what to happen. #[macro_use] pub mod macrodef; pub mod test_local_actor_system; pub mod test_local_actor_ref; pub mod test_probe; pub mod prelude;