Skip to content
Browse files

officially adding proxy generation support to mock-as3.

git-svn-id: https://mock-as3.googlecode.com/svn/trunk@28 bb1cc804-513f-0410-8659-4d20a89a40e2
  • Loading branch information...
1 parent babaf4a commit 3b82f781ccf2e3aea5ab6ae76a8b8d6aa22919dc firstbourne committed Oct 13, 2009
View
37 core/src/com/anywebcam/mock/MockInterceptor.as
@@ -0,0 +1,37 @@
+package com.anywebcam.mock
+{
+ import org.floxy.IInterceptor;
+ import org.floxy.IInvocation;
+
+ import com.anywebcam.mock.Mock;
+
+ public class MockInterceptor implements IInterceptor
+ {
+ public var mock:Mock;
+
+ public function MockInterceptor()
+ {
+ super();
+ }
+
+ public function intercept(invocation:IInvocation):void
+ {
+ if (invocation.property)
+ {
+ if (invocation.method.name == 'get')
+ {
+ invocation.returnValue = mock[invocation.property.name];
+ }
+ else
+ {
+ mock[invocation.property.name] = invocation.arguments[0];
+ invocation.returnValue = null;
+ }
+ }
+ else
+ {
+ invocation.returnValue = mock.invokeMethod(invocation.method.name, invocation.arguments);
+ }
+ }
+ }
+}
View
86 core/src/com/anywebcam/mock/Mockery.as
@@ -0,0 +1,86 @@
+package com.anywebcam.mock
+{
+ import asx.array.compact;
+ import asx.array.flatten;
+
+ import org.floxy.IInterceptor;
+ import org.floxy.IInvocation;
+ import org.floxy.IProxyRepository;
+ import org.floxy.ProxyRepository;
+
+ import com.anywebcam.mock.Mock;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+ import flash.system.ApplicationDomain;
+ import flash.utils.Dictionary;
+
+ public class Mockery extends EventDispatcher
+ {
+ private var mocksByTarget:Dictionary;
+ private var proxyRepository:IProxyRepository;
+ private var prepareProxyDispatchers:Array;
+ private var _nextNameIndex:int;
+
+ public function Mockery()
+ {
+ proxyRepository = new ProxyRepository();
+ prepareProxyDispatchers = [];
+ mocksByTarget = new Dictionary();
+
+ _nextNameIndex = 0;
+ }
+
+ public function prepare(... classes):void
+ {
+ classes = flatten(classes);
+
+ var dispatcher:IEventDispatcher = proxyRepository.prepare(classes, ApplicationDomain.currentDomain);
+ dispatcher.addEventListener(Event.COMPLETE, function(event:Event):void
+ {
+ dispatchEvent(event)
+ });
+ prepareProxyDispatchers.push(dispatcher);
+ }
+
+ public function nice(classToMock:Class, constructorArgs:Array=null):*
+ {
+ return create(classToMock, constructorArgs, false);
+ }
+
+ public function strict(classToMock:Class, constructorArgs:Array=null):*
+ {
+ return create(classToMock, constructorArgs, false);
+ }
+
+ public function create(classToMock:Class, constructorArgs:Array=null, nicely:Boolean=true, name:String=null):*
+ {
+ var interceptor:MockInterceptor = new MockInterceptor();
+ var target:* = proxyRepository.create(classToMock, constructorArgs || [], interceptor);
+ var mock:Mock = new Mock(target, nicely, name ? name : "Mockery$" + _nextNameIndex++);
+ interceptor.mock = mock;
+ mocksByTarget[target] = mock;
+ return target;
+ }
+
+ public function mock(target:Object):Mock
+ {
+ return mocksByTarget[target] as Mock;
+ }
+
+ public function verify(... targets):void
+ {
+ targets = compact(flatten(targets));
+
+ for each (var target:Object in targets)
+ {
+ var mock:Mock = mocksByTarget[target] as Mock;
+ if (mock)
+ {
+ mock.verify();
+ }
+ }
+ }
+ }
+}
View
28 core/src/com/anywebcam/mock/runner/InjectMockery.as
@@ -0,0 +1,28 @@
+package com.anywebcam.mock.runner
+{
+ import com.anywebcam.mock.Mockery;
+
+ import org.flexunit.internals.runners.statements.IAsyncStatement;
+ import org.flexunit.token.AsyncTestToken;
+
+ public class InjectMockery implements IAsyncStatement
+ {
+ private var mockery : Mockery;
+ private var mockeryFieldName : String;
+ private var target : Object;
+
+ public function InjectMockery(mockery : Mockery, mockeryFieldName : String, target : Object) {
+ this.mockery = mockery;
+ this.mockeryFieldName = mockeryFieldName;
+ this.target = target;
+ }
+
+ public function evaluate(parentToken:AsyncTestToken) : void {
+ //find mockery and inject
+ //trace("[mock-as3] Injecting [" + mockeryFieldName + "]");
+ target[mockeryFieldName] = mockery;
+
+ parentToken.sendResult(null);
+ }
+ }
+}
View
56 core/src/com/anywebcam/mock/runner/InjectMocks.as
@@ -0,0 +1,56 @@
+package com.anywebcam.mock.runner
+{
+ import asx.string.substitute;
+
+ import com.anywebcam.mock.Mockery;
+
+ import flash.utils.Dictionary;
+
+ import mx.logging.Log;
+
+ import org.flexunit.internals.runners.statements.IAsyncStatement;
+ import org.flexunit.token.AsyncTestToken;
+
+ public class InjectMocks implements IAsyncStatement {
+ private var mockery : Mockery;
+
+ [ArrayElementType("Dictionary")]
+ private var propertyNamesToInject : Array;
+
+ private var target : Object;
+
+ public function InjectMocks(mockery : Mockery, propertyNamesToInject : Array, target : Object) {
+ this.mockery = mockery;
+ this.propertyNamesToInject = propertyNamesToInject;
+ this.target = target;
+ }
+
+ public function evaluate(parentToken : AsyncTestToken) : void {
+ //find properties on target, inject using nice/strict and casting as klass
+ for each(var property : Dictionary in propertyNamesToInject) {
+ if (property["inject"]) {
+ var mock : Object = null;
+
+ if (property["type"] == "strict") {
+ mock = mockery.strict(property["klass"]);
+ }
+ else {
+ mock = mockery.nice(property["klass"]);
+ }
+
+ //trace(substitute("[mock-as3] Injecting '{}'={} {}",
+ // property["name"], mock, mockery.mock(mock)));
+
+ target[property["name"]] = mock as property["klass"];
+ }
+ else
+ {
+ //trace(substitute("[mock-as3] Skipping '{}'", property["name"]));
+ }
+ }
+
+ parentToken.sendResult(null);
+ }
+
+ }
+}
View
151 core/src/com/anywebcam/mock/runner/MockRunner.as
@@ -0,0 +1,151 @@
+package com.anywebcam.mock.runner
+{
+ import asx.array.*;
+ import asx.string.*;
+
+ import com.anywebcam.mock.Mockery;
+
+ import flash.utils.Dictionary;
+
+ import flex.lang.reflect.Field;
+ import flex.lang.reflect.Klass;
+
+ import org.flexunit.internals.runners.InitializationError;
+ import org.flexunit.internals.runners.statements.Fail;
+ import org.flexunit.internals.runners.statements.IAsyncStatement;
+ import org.flexunit.internals.runners.statements.StatementSequencer;
+ import org.flexunit.runner.notification.IRunNotifier;
+ import org.flexunit.runners.BlockFlexUnit4ClassRunner;
+ import org.flexunit.runners.model.FrameworkMethod;
+
+ public class MockRunner extends BlockFlexUnit4ClassRunner
+ {
+ private var mockery : Mockery;
+ private var mockeryFieldName : String;
+
+ [ArrayElementType("Dictionary")]
+ private var propertyNamesToInject : Array = [];
+
+ public function MockRunner(testClass : Class)
+ {
+ super(testClass);
+
+ this.mockery = new Mockery();
+ this.mockeryFieldName = findMockeryField(testClass);
+ this.propertyNamesToInject = findMocksToPrepareAndInject(testClass);
+
+ //trace("[mock-as3] MockRunner created for " + testClass.toString());
+ }
+
+ /**
+ * Used to find the field name of the property of type mockery
+ */
+ private function findMockeryField(testClass : Class) : String {
+ //need to check for feild of type com.anywebcam.mock.Mockery
+ var klass : Klass = new Klass(testClass);
+ var mockeries : Array = klass.fields.filter(function(field : Field, index : int, source : Array) : Boolean {
+ return field.type == Mockery;
+ });
+
+ //if none or more than 1, throw InitializationError
+ if (mockeries.length != 1) {
+ throw new InitializationError("Test class must have ONE property of type com.anywebcam.mock.Mockery!");
+ }
+
+ return mockeries[0].name;
+ }
+
+ /**
+ * Builds a configuration for all injectable properties
+ */
+ private function findMocksToPrepareAndInject(testClass : Class) : Array {
+ //build config maps in propertyNamesToInject
+ var klass : Klass = new Klass(testClass);
+ var injectionConfig : Array = [];
+
+ for each (var field : Field in klass.fields) {
+ if (field.hasMetaData("Mock")) {
+ var mockType : String = field.getMetaData("Mock", "type") || "nice";
+ if (!contains(["nice", "strict"], mockType)) {
+ throw new InitializationError(substitute("Property '{}' must declare a mock type of either 'nice' or 'strict'; '{}' is NOT a valid type.", field.name, mockType));
+ }
+
+ var injectable : String = field.getMetaData("Mock", "inject") || "true";
+ if (!contains(["true", "false"], injectable)) {
+ throw new InitializationError(substitute( "Property '{}' must declare the attribute inject as either 'true' or 'false'; '{}' is NOT valid.", field.name, injectable));
+ }
+
+ if(injectable == "true") {
+ var constructorParamLength : Number = new Klass(field.type).constructor.parameterTypes.length;
+ if (constructorParamLength != 0) {
+ throw new InitializationError(substitute("Cannot inject mock of type '{}' into property '{}'; constructor arguments are required! Please set inject='false' and manually create the mock in the [Before] for this test class.", field.type, field.name));
+ }
+ }
+
+ var property : Dictionary = new Dictionary(true);
+ property["name"] = field.name;
+ property["klass"] = field.type;
+ property["type"] = mockType;
+ property["inject"] = (injectable == "true");
+
+ //trace(substitute("[mock-as3] mock property:{} klass:{} type:{} inject:{}", field.name, field.type, mockType, injectable));
+
+ injectionConfig.push(property);
+ }
+ }
+
+ return injectionConfig;
+ }
+
+ /**
+ * Overriden from parent to implement hook to run mock preparation before [BeforeClass]
+ */
+ protected override function classBlock(notifier : IRunNotifier) : IAsyncStatement {
+ //build array of all class types to prepare
+ var classes : Array = compact(unique(pluck(this.propertyNamesToInject, "klass")));
+
+ //trace('[mock-as3] MockRunner.classBlock classes', classes.join(', '));
+
+ //create sequence where mockery is injected and classes are prepared
+ var sequencer:StatementSequencer = new StatementSequencer();
+ sequencer.addStep(new PrepareMockery(this.mockery, classes));
+ sequencer.addStep(super.classBlock(notifier));
+
+ return sequencer;
+ }
+
+ /**
+ * Overriden from parent to implement hook to run mockery and mock injection before [Before] and verify after [After]
+ */
+ protected override function methodBlock(method : FrameworkMethod) : IAsyncStatement {
+ //copy/paste of methodBlock from parent
+ //we need a shared instance of the test class to inject the properties, so we can't call super since it doesn't provide a handle
+ var test : Object = null;
+
+ try {
+ test = createTest();
+ }
+ catch (e : Error) {
+ trace(e.getStackTrace());
+ return new Fail(e);
+ }
+
+ var sequencer:StatementSequencer = new StatementSequencer();
+ //inject mockery and mocks before any befores executes
+ sequencer.addStep(new InjectMockery(mockery, mockeryFieldName, test));
+ sequencer.addStep(new InjectMocks(this.mockery, propertyNamesToInject, test));
+
+ //flow from base class
+ sequencer.addStep(withBefores(method, test));
+ sequencer.addStep(withDecoration(method, test ));
+ sequencer.addStep(withAfters(method, test));
+
+ //verify mocks after all afters executes
+ var propertiesToVerify : Array = pluck(this.propertyNamesToInject, "name");
+
+ sequencer.addStep(new VerifyMocks(method, this.mockery, propertiesToVerify, test));
+
+ return sequencer;
+ }
+ }
+}
View
51 core/src/com/anywebcam/mock/runner/PrepareMockery.as
@@ -0,0 +1,51 @@
+package com.anywebcam.mock.runner
+{
+ import com.anywebcam.mock.Mockery;
+
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.IEventDispatcher;
+ import flash.events.TimerEvent;
+ import flash.utils.Timer;
+
+ import org.flexunit.internals.runners.InitializationError;
+ import org.flexunit.internals.runners.statements.IAsyncStatement;
+ import org.flexunit.token.AsyncTestToken;
+
+ public class PrepareMockery implements IAsyncStatement {
+
+ public static const TIMEOUT : Number = 5000;
+
+ private var mockery : Mockery;
+
+ [ArrayElementType("Class")]
+ private var classesToPrepare : Array;
+
+ public function PrepareMockery(mockery : Mockery, classesToPrepare : Array) {
+ this.mockery = mockery;
+ this.classesToPrepare = classesToPrepare;
+ }
+
+ public function evaluate(parentToken : AsyncTestToken) : void {
+ if (classesToPrepare.length == 0) {
+ parentToken.sendResult(null);
+ }
+
+ var timer : Timer = new Timer(TIMEOUT);
+ timer.addEventListener(TimerEvent.TIMER, function(event : Event = null) : void {
+ timer.stop();
+ parentToken.sendResult(new InitializationError("Mock preparation timeout of " + TIMEOUT + "exceeded!"));
+ });
+
+ mockery.prepare(classesToPrepare);
+
+ mockery.addEventListener(Event.COMPLETE, function(event : Event) : void {
+ parentToken.sendResult(null);
+ });
+
+ mockery.addEventListener(ErrorEvent.ERROR, function(event : ErrorEvent) : void{
+ parentToken.sendResult(new InitializationError(event.text));
+ });
+ }
+ }
+}
View
53 core/src/com/anywebcam/mock/runner/VerifyMocks.as
@@ -0,0 +1,53 @@
+package com.anywebcam.mock.runner
+{
+ import com.anywebcam.mock.MockExpectationError;
+ import com.anywebcam.mock.Mockery;
+
+ import org.flexunit.internals.runners.statements.IAsyncStatement;
+ import org.flexunit.runners.model.FrameworkMethod;
+ import org.flexunit.token.AsyncTestToken;
+
+ public class VerifyMocks implements IAsyncStatement {
+ private var method : FrameworkMethod;
+
+ private var mockery : Mockery;
+
+ [ArrayElementType("String")]
+ private var propertyNames : Array;
+
+ private var target : Object;
+
+ public function VerifyMocks(method : FrameworkMethod, mockery : Mockery, propertyNames : Array, target : Object) {
+ this.method = method;
+ this.mockery = mockery;
+ this.propertyNames = propertyNames;
+ this.target = target;
+ }
+
+ public function evaluate(parentToken : AsyncTestToken) : void {
+ if (method.getSpecificMetaDataArg("Test", "verify") == "false") {
+ //trace("[mock-as3] Skipping mock verification for " + method.name + ".");
+ parentToken.sendResult(null);
+ return;
+ }
+
+ //iterate over all properties and call verify
+ var mocksToVerify : Array = propertyNames.map(function(property : String, index : int, source : Array) : Object {
+ return target[property];
+ });
+
+ try {
+ //trace("[mock-as3] Verifying expectations...", mocksToVerify.join(', '));
+ mockery.verify(mocksToVerify);
+ }
+ catch(mee : MockExpectationError) {
+ //if error is thrown catch it and pass it onto the parentToken
+ parentToken.sendResult(mee);
+ return;
+ }
+
+ parentToken.sendResult(null);
+ }
+
+ }
+}
View
70 core/test/MockTestRunner.mxml
@@ -1,28 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
- xmlns:mx="http://www.adobe.com/2006/mxml"
- xmlns="*"
- xmlns:flexunit="flexunit.flexui.*"
- creationComplete="onCreationComplete()">
+ styleName="flexUnitApplication"
+ creationComplete="runMe()"
+ xmlns:flexui="org.flexunit.flexui.*"
+ xmlns:mx="http://www.adobe.com/2006/mxml"
+ >
- <flexunit:TestRunnerBase
- id="testRunner"
- width="100%" height="100%"
- />
-
- <mx:Script>
- <![CDATA[
+ <mx:Script>
+ <![CDATA[
+ import com.anywebcam.mock.*;
+ import com.anywebcam.mock.runner.*;
- import flexunit.framework.*;
- import com.anywebcam.mock.*;
- import mockas3.*;
+ import org.flexunit.listeners.UIListener;
+ import org.flexunit.runner.FlexUnitCore;
- private function onCreationComplete():void
- {
- testRunner.test = MockFrameworkTest.suite();
- testRunner.startTest();
- }
- ]]>
- </mx:Script>
-
-</mx:Application>
+ private var core:FlexUnitCore;
+
+ // need to import the Class to support [RunWith("com.anywebcam.mock.runner.MockRunner")]
+ private var runner:MockRunner;
+
+ public function runMe():void
+ {
+ core = new FlexUnitCore();
+ core.addListener(new UIListener(uiListener));
+ core.run(
+ MockFrameworkTest,
+ MockRunnerExample
+ );
+ }
+ ]]>
+ </mx:Script>
+
+ <mx:Style>
+ Application {
+ backgroundColor: #3872b2;
+ backgroundGradientColors: #3872b2, #0c1a3d;
+ backgroundGradientAlphas: 1, 1;
+ themeColor: #ffffff;
+ color: #444444;
+ fontFamily: "Myriad Pro Semibold";
+ fontSize: 12;
+ }
+ </mx:Style>
+
+ <flexui:TestRunnerBase
+ id="uiListener"
+ width="100%"
+ height="100%"
+ />
+
+</mx:Application>
View
1 core/test/com/anywebcam/mock/MockFrameworkTest.as
@@ -19,6 +19,7 @@ package com.anywebcam.mock
ts.addTest( MockExpectationTest.suite() );
ts.addTest( MockTest.suite() );
ts.addTest( Examples.suite() );
+ ts.addTest( Examples.suite() );
return ts;
}
View
91 core/test/com/anywebcam/mock/MockRunnerExample.as
@@ -0,0 +1,91 @@
+package com.anywebcam.mock
+{
+ import com.anywebcam.mock.examples.Example;
+
+ import org.hamcrest.assertThat;
+ import org.hamcrest.core.*;
+ import org.hamcrest.object.equalTo;
+ import org.hamcrest.object.notNullValue;
+ import org.hamcrest.object.nullValue;
+ import org.hamcrest.object.instanceOf;
+
+ [RunWith("com.anywebcam.mock.runner.MockRunner")]
+ public class MockRunnerExample
+ {
+ public var mockery:Mockery;
+
+ [Mock(type="nice")]
+ public var nicelyImplicitlyInjected:Example;
+
+ [Mock(type="strict")]
+ public var strictlyImplicitlyInjected:Example;
+
+ [Mock(type="nice",inject="true")]
+ public var nicelyExplicitlyInjected:Example;
+ //
+ [Mock(type="strict",inject="true")]
+ public var strictlyExplicitlyInjected:Example;
+ //
+ [Mock(type="nice",inject="false")]
+ public var nicelyExplicitlyNotInjected:Example;
+
+ [Mock(type="strict",inject="false")]
+ public var strictlyExplicitlyNotInjected:Example;
+
+ [Before]
+ public function mockeryShouldBeAvailableInBefore():void
+ {
+ assertThat(mockery, notNullValue());
+ }
+
+ [Before]
+ public function mocksShouldBeAvailableInBefore():void
+ {
+ assertThat("nicely implicitly injected", nicelyImplicitlyInjected, notNullValue());
+ assertThat("strictly implicitly injected", strictlyImplicitlyInjected, notNullValue());
+ assertThat("nicely explicity injected", nicelyExplicitlyInjected, notNullValue());
+ assertThat("strictly explicity injected", strictlyExplicitlyInjected, notNullValue());
+ assertThat("nicely explicitly not injected", nicelyExplicitlyNotInjected, nullValue());
+ assertThat("strictly explicitly not injected", strictlyExplicitlyNotInjected, nullValue());
+ }
+
+ [Test]
+ public function mockeryShouldBeAvailableInTests():void
+ {
+ assertThat(mockery, notNullValue());
+ assertThat(mockery.mock(strictlyImplicitlyInjected), instanceOf(Mock));
+ }
+
+ [Test]
+ public function mocksShouldBeAvailableInTests():void
+ {
+ assertThat("nicely implicitly injected", nicelyImplicitlyInjected, notNullValue());
+ assertThat("strictly implicitly injected", strictlyImplicitlyInjected, notNullValue());
+ assertThat("nicely explicity injected", nicelyExplicitlyInjected, notNullValue());
+ assertThat("strictly explicity injected", strictlyExplicitlyInjected, notNullValue());
+
+ assertThat("nicely explicitly not injected", nicelyExplicitlyNotInjected, nullValue());
+ assertThat("strictly explicitly not injected", strictlyExplicitlyNotInjected, nullValue());
+ }
+
+ // Cannot use expected error for Mock Errors as the expects metadata is processed before the automatic mock verification
+ // [Test(expected="com.anywebcam.mock.MockExpectationError")]
+ [Test]
+ public function mocksShouldBeAutomaticallyVerified():void
+ {
+ var expected:String = "how long is a piece of string";
+
+ mockery.mock(strictlyImplicitlyInjected).method("giveString").withNoArgs.returns(expected).once;
+
+ assertThat(strictlyImplicitlyInjected.giveString(), equalTo(expected));
+ }
+
+ [Test(verify="false")]
+ public function mocksShouldNotBeAutomaticallyVerified():void
+ {
+ var expected:String = "how long is a piece of string";
+
+ mockery.mock(strictlyImplicitlyInjected).method("giveString").withNoArgs.returns(expected).once;
+ }
+ }
+}

0 comments on commit 3b82f78

Please sign in to comment.
Something went wrong with that request. Please try again.