Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making Friends with Mockolate

Making Friends with Mockolate

Avatar for drewbourne

drewbourne

April 17, 2012
Tweet

More Decks by drewbourne

Other Decks in Programming

Transcript

  1. Friends of Testing AS3, Flash, Flex FlexUnit, ASUnit Fluint, Hamcrest

    Mockolate, Mockito, ASMock FlexMonkey, RIATest, Ranorex Sprouts, Guard
  2. [Test] public function hasProperties_with_anonymous_objects():void { assertThat("Anonymous Objects", { id: 1,

    children: [2, 3] }, hasProperties({ id: 4, children: [2, 5] })); } Error: Anonymous Objects Expected: has properties "children":[<2>,<5>], "id":<4> but: property "children" was [<2>,<3>], property "id" was <1>
  3. [Test] public function hasProperties_with_nested_objects():void { assertThat("Nested Objects", { id: 1,

    children: [{ id: 2 }, { id: 3 }] }, hasProperties({ id: 4, children: [ hasProperties({ id: 2 }), hasProperties({ id: 5 }) ] })); } Error: Nested Objects Expected: has properties "children":[<has properties "id":<2>>,<has properties "id":<5>>], "id":<4> but: property "children" was [<[object Object]>,<[object Object]>], property "id" was <1>
  4. package org.hamcrest { public interface SelfDescribing { function describeTo(description:Description):void; }

    } package org.hamcrest { public interface Matcher extends SelfDescribing { function matches(item:Object):Boolean; function describeMismatch(item:Object, mismatchDescription:Description):void; } }
  5. [Test] public function dummy_values_fill_required_parameters():void { var UNUSED:String = "UNUSED"; var

    factory:IngredientFactory = new IngredientFactory(); assertThat(factory.create(UNUSED), instanceOf(Ingredient)); }
  6. public class FakeService { protected function respondWithAsyncResult(token:AsyncToken, result:*):AsyncToken { var

    event:ResultEvent = new ResultEvent(ResultEvent.RESULT, false, false, result, token); setTimeout(function():void { for each (var responder:IResponder in token.responders) { responder.result(event); } }, 0); return token; } protected function respondWithAsyncFault(token:AsyncToken, error:*):AsyncToken { var event:FaultEvent = new FaultEvent(FaultEvent.FAULT, false, false, fault, token); setTimeout(function():void { for each (var responder:IResponder in token.responders) { responder.fault(event); } }, 0); return token; } }
  7. [Test] public function saving_a_recipe():void { var recipe:Recipe = new Recipe();

    var command:SaveRecipeCommand = new SaveRecipeCommand(); command.dispatcher = new EventDispatcher(); command.service = new FakeRecipeService(); command.event = new SaveRecipeEvent(recipe); events.from(command.dispatcher).hasType(RecipeSavedEvent.SAVED); command.execute(); } public class FakeRecipeService extends FakeService implements IRecipeService { public function save(recipe:Recipe):AsyncToken { var savedRecipe:Recipe = recipe.clone(); savedRecipe.reset(); return respondWithAsyncResult(new AsyncToken(), savedRecipe); ! } }
  8. [Test] public function retrieve_a_list_of_recipes():void { var command:RetrieveRecipeListCommand = new RetrieveRecipeListCommand();

    command.dispatcher = new EventDispatcher(); command.service = new FakeRecipeService(); command.event = new RetrieveRecipeListEvent(); events.from(command.dispatcher) .hasType(RecipeListRetrievedEvent.RETRIEVED) .calls(function(event:RecipeListRetrievedEvent):void { assertThat(event.recipeList, instanceOf(RecipeList)); }); command.execute(); } public class FakeRecipeService extends FakeService implements IRecipeService { public function retrieveList(options:RecipeListOptions):Promise { var recipeList:RecipeList = new RecipeList(); var recipe:Recipe; for (var i:int = 0, n:int = 5; i < n; i++) { recipe = new Recipe(); // many lines of setting up fake data for this recipe. recipeList.add(recipe); } return respondWithAsyncResult(new AsyncToken(), recipeList); ! } }
  9. EventRule [Rule] public var events:EventRule = new EventRule(); [Test] public

    function events_example():void { // setup test events.from(dispatcher) .hasType(RecipeSavedEvent.SAVED) .hasProperties({ recipe: instanceOf(Recipe) }) .calls(function(event:Event):void { // verify event if necessary }) .once(); // execute test }
  10. [Test] public function modified_recipe_should_enable_saving():void { presenter = new EditRecipePresenter(); presenter.recipe

    = new ModifiedRecipe(); assertThat(presenter.saveEnabled, isTrue()); } [Test] public function unmodified_recipe_should_disable_saving():void { presenter = new EditRecipePresenter(); presenter.recipe = new UnmodifiedRecipe(); assertThat(presenter.saveEnabled, isFalse()); } internal class ModifiedRecipeStub extends Recipe { override public function isModified():Boolean { return true; } } internal class UnmodifiedRecipeStub extends Recipe { override public function isModified():Boolean { return false; } }
  11. [Test] public function saving_a_recipe():void { var recipe:Recipe = new Recipe();

    var service:MockRecipeService = new MockRecipeService(); service.saveCallback = function(recipe:Recipe):void { var deferred:Deferred = new Deferred(); deferred.reject(); return deferred.promise; } var command:SaveRecipeCommand = new SaveRecipeCommand(); command.dispatcher = new EventDispatcher(); command.service = service; command.event = new SaveRecipeEvent(recipe); events.from(command.dispatcher).hasType(RecipeSaveErrorEvent.ERROR); command.execute(); } internal class MockRecipeService implements IRecipeService { public var saveCallback:Function; public function save(recipe:Recipe):Promise { return saveCallback(recipe); } }
  12. [Test] public function saving_a_recipe():void { var dispatcher:EventDispatcherSpy = new EventDispatcherSpy();

    var command:SaveRecipeCommand = new SaveRecipeCommand(); command.dispatcher = dispatcher; command.execute(); assertThat("dispatched one event", dispatcher.dispatchedEvents, arrayWithSize(1)); assertThat("dispatched SAVED event", dispatcher.dispatchedEvents, hasItem(hasProperties({ type: RecipeSavedEvent.SAVED }))); } internal class EventDispatcherSpy extends EventDispatcher { private var _dispatchedEvents = []; public function get dispatchedEvents():Array { return _dispatchedEvents; } override public function dispatchEvent(event:Event):Boolean { _dispatchedEvents.push(event); return super.dispatchEvent(event); } }
  13. Clarify Interactions class CommercialKitchen implements IKitchen { public function cleanDishes():void

    { dishwasher.start(); } } class AutomaticDishwasher { public function start():void { door.lock(); hotwater.on(); timer.start(); // ... } } class MumsPartyKitchen implements IKitchen { public function cleanDishes():void { // noop, we're using paper plates. } }
  14. Reduce Dependencies [Test] public function cleaning_dishes_should_be_easy():void { kitchen = new

    CommercialKitchen(nice(IDishwasher)); kitchen.cleanDishes(); } class CommercialKitchen implements IKitchen { public function CommercialKitchen(dishwasher:IDishwasher) { _dishwasher = dishwasher; } public function cleanDishes():void { _dishwasher.start(); } } class AutomaticDishwasher implements IDishwasher { public function AutomaticDishwasher( timer:Timer, hotWater:IWaterSupply, electricity:IElectricitySupply, cleaningProduct:ICleaningProduct) { // ... skipped } }
  15. Interface Discovery public interface IKitchen { function cleanDishes():void; } public

    class CommercialKitchen { public function CommercialKitchen(dishwasher:IDishwasher) {} public function cleanDishes():void { dishwasher.start(); } } public interface IDishwasher { function start():void; } public class AutomaticDishwasher { public function AutomaticDishwasher( timer:ITimer, waterSupply:IWaterSupply, electricitySupply:IElectricitySupply) {} public function start():void { timer.start(); } }
  16. Exploratory Testing [Test] public function what_ingredients_is_the_chef_asking_for():void { // Chef, and

    Kitchen are blackboxes. var chef:Chef = new Chef(); var kitchenSpy:KitchenSpy = new KitchenSpy(); var meal:Meal = chef.prepareMeal(kitchen); trace("which ingredients did the chef use?", ObjectUtil.toString(kitchenSpy.ingredientsUsed)); } public class KitchenSpy extends Kitchen { public var ingredientsUsed:Array = []; override public function findIngredient(name:String):Ingredient { var ingredient:Ingredient = super.findIngredient(name); ingredientsUsed.push({ name: name, ingredient: ingredient }); return ingredient; } }
  17. Test Structure Prepare Proxy Classes Setup Create Mock Objects Define

    Expectations Execute Assert Verify Expectations
  18. Test Structure Prepare Proxy Classes Setup Create Mock Objects Define

    Expectations Execute Assert Verify Expectations
  19. Implementing Mock Objects Reflect Class Generate Proxy Class Load Bytecode

    Inject Interceptor Intercept Invocations Execute Expectations Verify Expectations
  20. Generated Proxy Example package mockolate.utensils { public interface Scoopable {

    function scoop(ingredient:Ingredient, scalar:int = 1):Quantity; } } package mockolate.utensils.generated { public class ScoopableProxy implements Scoopable { private var _interceptor:Interceptor; public function ScoopableProxy(interceptor:Interceptor) { _interceptor = interceptor; super(); } public function scoop(ingredient:Ingredient, scalar:int = 1):Quantity { return _interceptor.intercept(new Invocation( this, 'method', 'public', 'scoop', [ingredients, scalar], null)); } } }
  21. Generated Proxy Example package mockolate.utensils { public class Tablespoon implements

    Scoopable { public function scoop(ingredient:Ingredient, scalar:int = 1):Quantity { return new Quantity(Unit.TABLESPOON, scalar, ingredient); } } } package mockolate.utensils.generated { public class TablespoonProxy extends Tablespooon { private var _interceptor:Interceptor; public function TablespoonProxy(interceptor:Interceptor) { _interceptor = interceptor; super(); } override public function scoop(ingredient:Ingredient, scalar:int = 1):Quantity { return _interceptor.intercept(new Invocation( this, 'method', 'public', 'scoop', [ingredients, scalar], super.scoop)); } } }
  22. Preparing Proxies using FlexUnit 4.1 package mockolate.examples { public function

    PreparingWithFlexUnit { [Rule] public var mocks:MockolateRule = new MockolateRule(); [Mock] public var service:IRecipeService; [Mock] public var dispatcher:IEventDispatcher; private var command:SaveRecipeCommand; [Before] public function setup():void { command = new SaveRecipeCommand(); command.dispatcher = dispatcher; command.service = service; } } }
  23. Preparing Proxies for everything else package mockolate.examples { public class

    PreparingForEverythingElse { private var preparer:IEventDispatcher public function setup():void { preparer = prepare(IRecipeService, IEventDispatcher); preparer.addEventListener(Event.COMPLETE, prepareComplete); } private function prepareComplete():void { // create Mock Objects, Stubs, Spies } } }
  24. Creating Mock Objects package mockolate.examples { public class CreatingMockObjects {

    [Rule] public var mocks:MockolateRule = new MockolateRule(); [Mock] public var aNiceRecipeService:IRecipeService; [Mock(type="nice")] public var anotherNiceRecipeService:IRecipeService; [Mock(type="strict")] public var aVeryStrictChef:Chef; [Mock(type="partial")] public var aPartialKitchen:Kitchen; [Test] public function in_other_cases():void { aNiceRecipeService = nice(IRecipeService); anotherNiceRecipeService = nice(IRecipeService); aVeryStrictChef = strict(Chef); aPartialKitchen = partial(Kitchen); } } }
  25. Creating Mock Objects package mockolate.examples { public class CreatingMockObjects {

    [Test] public function attempting_to_create_an_unprepared_class():void { var friend:Friend = nice(Friend); } } } ArgumentError: No proxy class prepared for mockolate.examples::Friend
  26. nice(Class) [Test] public function unknown_invocation_with_nice_mock_object():void { var chef:Chef = nice(Chef,

    "Nigella Lawson"); var kitchen:Kitchen = nice(Kitchen); var meal:Meal = chef.prepareMeal(kitchen); assertThat("expecting a delicious Meal", meal, instanceOf(Meal)); } Error: expecting a delicious Meal Expected: an instance of mockolate.examples::Meal but: was null
  27. strict(Class) [Test] public function unknown_invocation_with_strict_mock_object():void { var chef:Chef = strict(Chef,

    "Gordon Ramsey"); var kitchen:Kitchen = nice(Kitchen); var meal:Meal = chef.prepareMeal(kitchen); assertThat("expecting a delicious Meal", meal, instanceOf(Meal)); } Error: No Expectation defined for mockolate.examples::Chef(Gordon Ramsey).prepareMeal(< [object Kitchen76D845B3B7C233645BB7006A92386336F7A5E215]>)
  28. partial(Class) public interface IChef { function prepareMeal(kitchen:Kitchen):Meal; } [Test] public

    function unknown_invocation_with_partial_mock_object_of_interface():void { var chef:IChef = partial(IChef, "Heston Blumenthal"); var kitchen:Kitchen = nice(Kitchen); var meal:Meal = chef.prepareMeal(kitchen); assertThat("expecting a delicious Meal", meal, instanceOf(Meal)); } Error: Cannot proceed on method because it is an interface method: mockolate.generated:IChefBC4E5F6D949F4FB02ED3B5E8C6471E5AA3B578C9/prepareMeal
  29. partial(Class) public class Chef { public function prepareMeal(kitchen:Kitchen):Meal { return

    new Meal("Something Delicious"); } } [Test] public function unknown_invocation_with_partial_mock_object():void { var chef:Chef = partial(Chef, "Heston Blumenthal"); var kitchen:Kitchen = nice(Kitchen); var meal:Meal = chef.prepareMeal(kitchen); assertThat("expecting a delicious Meal", meal, instanceOf(Meal)); }
  30. verify(mockObject) public class Chef { public function prepareMeal(usingKitchen:Kitchen):Meal { return

    new Meal("Scrabbled Eggs"); } } [Test] public function verify_with_unmet_expectation():void { var chef:Chef = new Chef(); var kitchen:Kitchen = nice(Kitchen); expect( kitchen.prepare(arg(Recipe), arg(chef)) ).once(); var meal:Meal = chef.prepareMeal(kitchen); verify(kitchen); } // 1 unmet Expectation mockolate.example::Kitchen#prepare(<[class Recipe]>,<[object Chef] >); should to be invoked <1> times but was invoked <0> times
  31. verify(mockObject) [Mock] public var kitchen:Kitchen; [Test(verify="false")] public function not_verified():void {

    var chef:Chef = new Chef(); var meal:Meal = chef.prepareMeal(kitchen); expect( kitchen.prepare(arg(Recipe), arg(chef)) ).once(); var meal:Meal = chef.prepareMeal(kitchen); } // no error
  32. public interface Flavour { function get name():String; function set quality(value:Quality):void;

    function combine(flavour:Flavour):Flavour; } [Test] public function defining_expectation_using_invocation():void { expect( coffee.name ).returns( "Coffee" ); expect( coffee.quality = Quality.HIGH ); expect( coffee.combine(chocolate) ).returns( mocha ); allow( chocolate.combine(orange) ).returns( jaffa ); } [Test] public function defining_expectation_using_strings():void { mock(coffee).getter("name").returns("Coffee"); mock(coffee).setter("quality").arg(Quality.HIGH); mock(coffee).method("combine").args(chocolate).returns(mocha); stub(coffee).method("combine").args(chocolate).returns(mocha); }
  33. [Test] public function using_expect():void { expect( flavour.name ).returns( "Chocolate" ).once();

    } // 1 unmet Expectation mockolate.examples::Flavour(flavour)#name; should to be invoked <1> times but was invoked <0> times. [Test] public function using_allow():void { allow( flavour.name ).returns( "Chocolate" ).once(); } // No Error. [Test] public function using_mock():void { mock( flavour ).getter( "name" ).returns( "Chocolate" ).once(); } // 1 unmet Expectation mockolate.examples::Flavour(flavour)#name; should to be invoked <1> times but was invoked <0> times. [Test] public function using_stub():void { stub( flavour ).getter( "name" ).returns( "Chocolate" ).once(); } // No Error.
  34. expect( target.method(_, _) ) expect( target.setter = _ ) expect(

    target.method(arg(_), arg(_)) ) expect( target.setter = arg(_) ) mock( target ).method( name ).args(_, _) mock( target ).method( name ).anyArgs() mock( target ).method( name ).noArgs() mock( target ).setter( name ).arg(_)
  35. arg(value) arg(matcher) [Test] public function arguments_by_value():void { var coffee:Flavour =

    nice(Flavour, "coffee"); var chocolate:Flavour = nice(Flavour, "chocolate"); var mocha:Flavour = nice(Flavour, "mocha"); expect( coffee.combine(chocolate) ).returns( mocha ); expect( coffee.combine(arg(chocolate)) ).returns( mocha ); expect( coffee.combine(arg(equalTo(chocolate))) ).returns( mocha ); }
  36. arg(value) arg(matcher) [Test] public function arguments_by_class():void { var coffee:Flavour =

    nice(Flavour, "coffee"); var unknown:Flavour = nice(Flavour, "unknown"); expect( coffee.combine(arg(Flavour)) ).returns( unknown ); expect( coffee.combine(arg(instanceOf(Flavour))) ).returns( unknown ); }
  37. arg(value) arg(matcher) [Test] public function arguments_by_matcher():void { var coffee:Flavour =

    nice(Flavour, "coffee"); var chocolate:Flavour = nice(Flavour, "chocolate"); var mocha:Flavour = nice(Flavour, "mocha"); expect( chocolate.name ).returns("Chocolate"); expect( coffee.combine(arg(hasProperties({ name: "Chocolate" }))) ).returns( mocha ); }
  38. .returns(value) .returns(v1, v2, ...vN) [Mock] public var coffeeMachine:CoffeeMachine; [Test] public

    function returning_a_value():void { var order:CoffeeOrder = new CoffeeOrder(); expect( coffeeMachine.make(order) ) .returns( new FlatWhite() ); assertThat(coffeeMachine.make(order), instanceOf(FlatWhite)); assertThat(coffeeMachine.make(order), instanceOf(FlatWhite)); } [Test] public function returning_a_sequence_of_values():void { var order:CoffeeOrder = new CoffeeOrder(); expect( coffeeMachine.make(order) ) .returns( new Latte(), new Cappucino() ); assertThat(coffeeMachine.make(order), instanceOf(Latte)); assertThat(coffeeMachine.make(order), instanceOf(Cappucino)); assertThat(coffeeMachine.make(order), instanceOf(Cappucino)); }
  39. .throws(error) [Test] public function throwing_an_error():void { var order:CoffeeOrder = new

    CoffeeOrder(); expect( coffeeMachine.make(order) ) .throws( new OutOfMilkError() ); try { coffeeMachine.make(order); fail("expected OutOfMilkError"); } catch (error:OutOfMilkError) { } }
  40. .answers(Answer) [Test] public function using_async_token_and_responders():void { var token:AsyncToken = new

    AsyncToken(); var result:Array = [new Recipe(), new Recipe()]; expect( recipeService ).returns(token).answers(withResult(token, result)); } public function withResult(token:AsyncToken, result:*):Answer { return new ResultAnswer(token, new ResultEvent(ResultEvent.RESULT, false, false, result, token)); } public class ResultAnswer { public function invoke(invocation:Invocation):* { setTimeout(applyResult, _delay); return undefined; } protected function applyResult():void { _token.mx_internal::applyResult(_resultEvent); } }
  41. .dispatches(event) .dispatches(event, delay) [Test] public function dispatching_an_event():void { var order:CoffeeOrder

    = new CoffeeOrder(); expect( coffeeMachine.make(order) ) .dispatches(new Event(Event.COMPLETE)); } [Test] public function dispatching_an_event_with_a_delay():void { var order:CoffeeOrder = new CoffeeOrder(); expect( coffeeMachine.make(order) ) .dispatches(new ProgressEvent(25), 10) .dispatches(new ProgressEvent(50), 20) .dispatches(new ProgressEvent(75), 30) .dispatches(new Event(Event.COMPLETE), 40); }
  42. .calls(function) [Test] public function calling_a_function():void { var order:CoffeeOrder = new

    CoffeeOrder(); expect( coffeeMachine.make(order) ) .calls(function(a:String, b:String):void { trace('CoffeeMachine.make(order) was called', a, b); }, ['yay', 'hooray']); }
  43. .callsWithArguments(function) [Test] public function calling_a_function_with_arguments():void { var order:CoffeeOrder = new

    CoffeeOrder(); expect( coffeeMachine.make(order) ) .callsWithArguments(function(receivedOrder:CoffeeOrder):void { trace('CoffeeMachine.make(order) was called with ', receivedOrder); }); }
  44. .callsWithInvocation(function) [Test] public function calling_a_function_with_invocation():void { var order:CoffeeOrder = new

    CoffeeOrder(); expect( coffeeMachine.make(order) ) .callsWithInvocation(function(invocation:Invocation):void { trace('CoffeeMachine.make(order)'. ' was called on', invocation.target, ' with', invocation.arguments); }); }
  45. .inSequence(sequence) [Test] public function cook_should_prepare_recipe_in_correct_sequence():void { var cook:Cook = new

    ShortOrderCook(); var crackEggs:RecipeStep = nice(RecipeStep "crackEggs"); var addMilk:RecipeStep = nice(RecipeStep "addMilk"); var whisk:RecipeStep = nice(RecipeStep "whisk"); var recipe:Recipe = new Recipe("Scrabbled Eggs"); recipe.steps = [ crackEggs, addMilk, whisk ]; // prepare steps of a recipe in order var recipeSequence:Sequence = sequence("recipeSequence"); expect( step1.execute() ).inSequence(recipeSequence); expect( step2.execute() ).inSequence(recipeSequence); expect( step3.execute() ).inSequence(recipeSequence); cook.prepare(recipe); verify(crackEggs); verify(addMilk); verify(whisk); }
  46. .when(inState) .then(becomesState) public var blender:Blender; [Mock] public var motor:Motor; [Mock]

    public var control:Control; [Test] public function blender_should_adjust_speed_when_on():void { var power:States = states('power').startsAs('off'); expect( control.switchOn() ).then( power.isStateOf('on') ).once(); expect( motor.adjustSpeed(1) ).when( power.isNot('on') ).never(); expect( motor.adjustSpeed(1) ).when( power.isStateOf('on') ).once(); expect( control.switchOff() ).then( power.isStateOf('off') ).once(); expect( motor.adjustSpeed(0) ).when( power.isStateOf('off') ).once(); blender = new Blender(motor, control); blender.switchOn(); }
  47. Spying on Invocations [Test] public function spying_on_invocations():void { var methodSpy:Spy

    = spy( flavour.combine(arg(Flavour)) ); var getterSpy:Spy = spy( flavour.name ); var setterSpy:Spy = spy( flavour.name = arg(anything()) ); }
  48. [Mock(type="partial")] public var dispatcher:EventDispatcher; [Test] public function using_test_spies():void { var

    addSpy:Spy = spy(dispatcher.addEventListener( arg(anything()), arg(Function), arg(Boolean), arg(Number), arg(Boolean))); var removeSpy:Spy = spy(dispatcher.removeEventListener( arg(anything()), arg(Function), arg(Boolean)))); var listener1:Function = function(event:Event):void {}; var listener2:Function = function(event:Event):void {}; dispatcher.addEventListener(Event.COMPLETE, listener1); dispatcher.addEventListener(Event.COMPLETE, listener2); dispatcher.removeEventListener(Event.COMPLETE, listener1); assertThat("all added event listeners were removed", addSpy.invocations.length, equalTo(removeSpy.invocation.length)); }
  49. Spying on Arguments [Test] public function spying_on_arguments():void { var addSpy:Spy

    = spy(dispatcher.addEventListener( arg(anything()), arg(Function), arg(Boolean), arg(Number), arg(Boolean))); dispatcher.addEventListener("start", function(event:Event):void {}); dispatcher.addEventListener("progress", function(event:Event):void {}); dispatcher.addEventListener("end", function(event:Event):void {}); trace('added events:')); for each (var args:Array in addSpy.arguments) { trace('\ttype:', args[0], 'weak:', args[4]); } assertThat("'start' was added", addSpy.calledWith(equalTo("start"), Function, true, 0, false), isTrue()); assertThat("'cancel' was not added", addSpy.neverCalledWith(equalTo("cancel"), Function, true, 0, false), isTrue()); }
  50. Spying on Return Values [Test] public function spying_on_returned_values():void { var

    min:Number = 0; var max:Number = 20; var rng:RandomNumberGenerator = partial(RandomNumberGenerator, "rng", [min, max]); var rngSpy:Spy = spy(rng.next()); for (var i:int = 0, n:int = 5; i < n; i++) { rng.next(); } trace('returned values', rngSpy.returnValues); assertThat("always returned a valid Number in range", rngSpy.returnValues, everyItem(between(min, max))); }
  51. Spying on Errors public class ErrorProne { public function accept(value:Object):void

    { if (!value) throw new ArgumentError("ErrorProne#accept requires a value"); } } [Test] public function spying_on_thrown_errors():void { var errorProne:ErrorProne = partial(ErrorProne, "errorProne"); var errorSpy:Spy = spy(errorProne.accept(arg(anything()))); try { errorProne.accept(false); } catch (e:Error) { } assertThat(errorSpy.threw(ArgumentError)); }
  52. Invocation Count [Test] public function spying_on_invocation_counts():void { var addSpy:Spy =

    spy(dispatcher.addEventListener( arg(anything()), arg(Function), arg(Boolean), arg(Number), arg(Boolean))); var listener1:Function = function(event:Event):void {}; var listener2:Function = function(event:Event):void {}; dispatcher.addEventListener(Event.COMPLETE, listener1); dispatcher.addEventListener(Event.COMPLETE, listener2); assertThat("should have added two event listeners", addSpy.calledTwice); }
  53. Don’t Mock Everything [Test] public function mocking_everything():void { var recipe:Recipe

    = nice(Recipe); var event:SaveRecipeEvent = nice(SaveRecipeEvent); var dispatcher:IEventDispatcher = nice(IEventDispatcher); var service:IRecipeService = nice(IRecipeService); var token:AsyncToken = nice(AsyncToken); var command:SaveRecipeCommand = new SaveRecipeCommand(); command.dispatcher = dispatcher; command.service = service; command.event = event; expect( event.recipe ).returns(recipe); expect( recipe.isModified ).returns(true); expect( service.saveRecipe(recipe) ).returns(token); expect( token.addResponder(arg(Responder)) ); events.from(command.dispatcher).hasType(RecipeSavedEvent.SAVED); command.execute(); }
  54. Mocking Runtime Classes Yes: Sprite, MovieClip, URLLoader, Loader, IEventDispatcher, I*

    No: Stage, Graphics, NetConnection, NetStream, NetGroup
  55. Favour Interfaces public interface IFridgeView { function open():void; function close():void;

    } public class FridgeView extends UIComponent implements IFridgeView { public function open():void { } public function close():void { } }
  56. Stub Queries Mock Actions public interface IRecipeModel { function getRecipes():RecipeList;

    } [Mock] public var recipeModel:IRecipeModel; [Test] public function stub_queries():void { var recipes:RecipeList = new RecipeList(); recipes.add(new Recipe()); recipes.add(new Recipe()); recipes.add(new Recipe()); allow( recipeModel.getRecipes() ).returns( recipes ); }
  57. Stub Queries Mock Actions public interface IRecipeService { function retrieveList():AsyncToken;

    } [Mock] public var recipeSerivce:IRecipeService; [Test] public function mock_actions():void { var token:AsyncToken = new AsyncToken(); expect( recipeService.retrieveList() ).returns( token ); }
  58. Only mock your closest friends [Test] public function dont_mock_trainwrecks():void {

    var kitchen:Kitchen = nice(Kitchen); var dishwasher:Dishwasher = nice(Dishwasher); var onOffSwitch:OnOffSwitch = nice(OnOffSwitch); expect( kitchen.dishwasher ).returns( dishwasher ); expect( dishwasher.onOffSwitch ).returns( onOffSwitch ); expect( onOffSwitch.on() ).once(); // ... }