You are on page 1of 33

IOC Containers and Unit

testing.
How they improve TDD &
Architecture
Purpose to demonstrate and promote the
utility of IOC Containers for improving
primarily unit testing coverage.
First must show that container
injection has advantages over
manual injection wrt to looser
coupling in classes.
Manual Injection - Is ok. Especially if the dependency (helper)
is created and destroyed with the class using it.

In ClassA In Helper

public var model:Model; public var helperModel:


..... HelperModel

public var helper:Helper; //Constructor


private var helperModel:
HelperModel = model. public function Helper()
helperModel ; {
..... .....
}
helper = new Helper();
helper.helperModel = We are defining helperModel and then using
helperModel; this definition to set helperModel on the
helper.
But it does couple Model and
HelperModel In Class A.
With a container we could have the two classes uncoupled(So
that ClassA has no reference to her HelperModel) and isolated
- especially helpful for unit testing. And preventing even tighter
coupling!
Container Injection - no coupling.

In ClassA In Helper

public var model:Model; [Inject]


public var helperModel:
..... HelperModel

public var helper:Helper; //Constructor

...... public function Helper()


helper = new Helper(); {

}
TDD often requires some form of injection in order to build
effective unit tests. This is due to the fact that in order to run a
test on a piece of code, often the dev will need to pass the code a
dummy or null object or a stub.
This dummy null or stub object
needs to be created somewhere
and then injected.

We have various options all of


which are valid:

1. Use a StubDelegate with the


same interface as the
Delegate.
2. Create the stub in the
testcase class.
3. Have Stubs for a class or
suite in a Config class and
use a container.
Use a StubDelegate with the same
interface as the Delegate.
If using Cairngorm this usually means that the stub is passed
across several classes (by injection) before it's injected into the
class with the method under test.

This means that all those classes need to be created and work
correctly before we can do tdd for our method.

This also encourages the


traversal injection approach.
Create the stub in the testcase class.
Better for unit tests as you avoid passing stub data across (not better for
functional tests).

I could instantiate the stub in the testCase, but it may be used again, provided
devs are aware of it and if I put it in a config file it would make other
developers aware of it more easily. Rather than wasting time searching for it.
Without a config file once found this stubdata is often copy pasted into
another test case, potentially duplicating errors and bad coding standards
flagged by FlexCPD.

With a central repository of useful objects they can refer to in writing later
tests.

It's also good OOP to separate object creation from its use.
This is the utility of the container and in doing it
this way we encourage dependency injection rather than procedural
programming where it applies.
Create the stub in the testcase class. More difficult
for another developer to locate objects for reuse.
private function createStubMetricData():void
{
//Metric 1
var metadataItem1:ProductMetaDataVO = new ProductMetaDataVO();
metadataItem1.CompositeScoreWeight = 1;
var metric1:Object = {};
metric1.name = "metric1";
metric1.metadataItem = metadataItem1;

//Metric 2
var metadataItem2:ProductMetaDataVO = new ProductMetaDataVO();
metadataItem2.CompositeScoreWeight = 1;
var metric2:Object = {};
metric2.name = "metric2";
metric2.metadataItem = metadataItem2;

//Metric 3
var metadataItem3:ProductMetaDataVO = new ProductMetaDataVO();
metadataItem3.CompositeScoreWeight = 0;
var metric3:Object = {};
metric3.name = "metric3";
metric3.metadataItem = metadataItem3;

stubMetricData.push(metric1);
stubMetricData.push(metric2);
stubMetricData.push(metric3);
}
With a container this becomes...
Context (Config Class) Test Case Class
public var stubMetricData:Array; //constructor
TestCase ():void
...... {
stubMetricData = createStubMetricData(); addContext()
}
private function createStubMetricData():Array
{ public function setup()
{
...as before.... addContext()
}
return stubMetricDataArray;
......
} [Inject]
public var stubMetricData;
//Now this can be easily found and reused.
private function addContext():void
{
var context : Context = ActionScriptContextBuilder.
build(ExampleASConfig); context.
createDynamicContext().addObject(this);
}
What's the difference between all these
tests!

SEE!
http://www.javaranch.
com/journal/200603/EvilUnitTests
.html#toofunc
Containers for Unit Testing

A Container makes it easier to isolate each class by injecting


stubs directly into the class.

Classes with method to test.

class A

model

helperModel
Rather than this which often arises from
manual dependency injection.
These 'unit tests' are really functional tests the lines represent the
dependencies.
class A

Classes with method to test.

model
Classes depended on that
should be stubbed.

helperModel

The lines represent


dependencies like model.
helperModel
Debugging and writing true unit
tests this way is more
straightforward than.......
... This way these are actually
functional tests.

Classes with method to test.

Classes depended on that


should be stubbed.

Nothing wrong with a few of them


done by developers.
Thirdly class architecture.

vs

We do it this way. Better this way


With container injection this is
straightforward.

With the componentized


approach to the Presentation
Model pattern, there is a single
hierarchy of view components but
no hierarchy of presentation
models. Each view component
has a corresponding presentation
model, but these models are
independent of one another.
They do need coordination via
a domain model. See links.
We usually do it this way.

With this approach, the top-level


PM is usually instantiated in the
top level view. It may be a
singleton to allow other parts of
the application, such as
commands to access it as
required. The child PMs are then
passed down manually into the
child views, as shown below:
We do it this way, manual
injection (and coupled).

<example:MyComponent ... >

<mx:Script>

[Bindable]
public var model:MyComponentPM;

</mx:Script>

<example:MyChildComponent model="{ model.myChildComponentPM }" ... />

...

</example:MyComponent/>
Better this way with container injection looser coupling no
reference to child componentPM
<example:MyComponent ...
addedToStage="dispatchEvent(new Event('configureIOC', true'))">

<mx:Script>

[Inject]
[Bindable]
public var model:MyComponentPM;

</mx:Script>

<example:MyChildComponent/>

...

</example:MyComponent/>
Why use containers - Agility!

A Container allows us to be more agile.


Starting TD development before a contract is defined and
stubDelegate is complete.

By creating smaller stubs that involve only units under test.


Our code may need refactoring to ensure this.
Container - decoupling

The container promotes decoupling which produces better


structured code.

Links see other document.


Container facilates reuse.

One thing that would have definitely been reused is a context


(config file) that contains the configurationSettings and
localisedSettings. Currently there are many local instantiations.
Containers - Isolation

This leads to a focus on true unit testing and less functional


testing that passes a stub delegate across the Cairngorm
sequence if there's a problem with the sequence we have a
problem with our method under test, by passing the
dependency manually.
Containers - Clarity

Config becomes a central repository of stubs for a particular


test case that other developers can easily be made aware of
and peruse rather than looking through test methods which is
where they are currently.
Better Isolation means better methods!
ChildPMTest ChildPM

public function testUpdateModel():void public function updateModel(value:*='*'):void


{ {
if(value!='*')value='@';
var childVO:ChildVO = childPM.vo as ChildVO;
childVO.update(value);

childPM.updateModel('@'); //Don't do this its then doing two things at


once implicitly;
assertEquals('This is the //if(parentPM)
//parentPM.updateModel('*');
childVO@', childVO.title);
} //Instead do this you can test this!!!
updateParentModel(value);
//really a functional test. }
/* public function testUpdateParentModel():void
{ public function updateParentModel(value:*):void
var parentVO:ParentVO = parentPM. {
vo as ParentVO; if(parentPM)
childPM.updateParentModel('@') parentPM.updateModel(value);
assertEquals('This is a parentVO it }
has the child VO@',parentVO.id);

}
*/
A setup method that should be made of a context file(s) and
injected.
override public function setUp():void
{
//This stuff should be in a ContextFile.
var localisedSettings:LocalisedSettings = new LocalisedSettings({});
localisedSettings.addProperties({'dh_Ranger_Thousand_Separator':','});
localisedSettings.addProperties({'dh_Ranger_Decimal_Separator':'.'});
var configurationSettings:Object = {'dh.ranger.flex.defaultcurrencysymbol':'£','dh.ranger.flex.currencysymbolalign':'L'};

presentationModel = new ProductGroupsModulePM(1, configurationSettings, localisedSettings);


presentationModel.dendrogramData = new DendrogramData([],{},[]);

//CREATES SEG DATA ON MODEL.


createSegmentationData();

var level1GroupVO:GroupVO = new GroupVO();


level1GroupVO.GroupId = 10;

level1GroupVO.GroupProducts = createFourGroupedProductsNoneExcluded();

groupVO = level1GroupVO;
presentationModel.groups.addItem(level1GroupVO);

var productDataAttributeVO1:ProductDataAttributeVO = new ProductDataAttributeVO();


productDataAttributeVO1.AttributeValue = "1000";
productDataAttributeVO1.ColumnName = "test";
productDataAttributeVO1.ProductCode = "12345";

var productDataAttributeVO2:ProductDataAttributeVO = new ProductDataAttributeVO();


productDataAttributeVO2.AttributeValue = "2000";
productDataAttributeVO2.ColumnName = "test";
productDataAttributeVO2.ProductCode = "12346";

var productDataAttributeVO3:ProductDataAttributeVO = new ProductDataAttributeVO();


productDataAttributeVO3.AttributeValue = "1000";
productDataAttributeVO3.ColumnName = "test";
productDataAttributeVO3.ProductCode = "12347";

var productDataAttributeVO4:ProductDataAttributeVO = new ProductDataAttributeVO();


productDataAttributeVO4.AttributeValue = "1000";
productDataAttributeVO4.ColumnName = "test";
productDataAttributeVO4.ProductCode = "12348";

var attrs1:Object = { "test": productDataAttributeVO1};


var attrs2:Object = { "test": productDataAttributeVO2};
var attrs3:Object = { "test": productDataAttributeVO3};
var attrs4:Object = { "test": productDataAttributeVO4};

var productCodeReferenced_productDataAttributeVO_ObjectProxies:Array =
[
new ObjectProxy({ "12345": attrs1 }), new ObjectProxy({ "12346": attrs2}), new ObjectProxy({ "12347": attrs3 }), new ObjectProxy({ "12348": attrs4 })
];
var productData:Array = productCodeReferenced_productDataAttributeVO_ObjectProxies;

var productMetaDataVO:ProductMetaDataVO = new ProductMetaDataVO();


productMetaDataVO.ColumnName = "test";
productMetaDataVO.IsVisible = true;
productMetaDataVO.ResourceKey = "test";
productMetaDataVO.FormatType = FormatTypeVO.NUMBER;
productMetaDataVO.FormatPrecision = 2;
productMetaDataVO.FormatMagnitude = null;
productMetaDataVO.VisibleToProductMetrics = true;
productMetaDataVO.VisibleToGroupMetrics = true;
productMetaDataVO.VisibleToGroupSummary = true;
productMetaDataVO.VisibleToProductSubstitutes = true;
Having a setup with reusable stubs.

override public function setUp() : void


{
var context : Context
= ActionScriptConte
xtBuilder.build(ExampleASConfig);
context.createDynamicContext().addObject(this);
}
The Container - Formalises DI

One can look at a class and distinguish more easily between


depended on and composite objects - ones that live and die
with the class as opposed to those that live on (aggregate). The
[Inject] metadata tag makes highlight the classes
dependencies.
Links http://www.spicefactory.
org/parsley/
http://opensource.adobe.
com/wiki/display/cairngorm/OptionsConstructRetrieveIsolateObj
ects

http://www.javaranch.com/unit-testing/
http://opensource.adobe.
com/wiki/display/cairngorm/BestPracticesAgileUnitTesting

http://www.javaranch.com/unit-testing/too-functional.jsp

http://www.javaranch.com/unit-testing/software-testing-
vocabulary.jsp
Links

The container also facilitates a more decoupled


componentized structure for presentation models see:
http://blogs.adobe.
com/tomsugden/2009/08/applying_the_presentation_mode.
html

http://www.adobe.com/devnet/flex/articles/ioc_frameworks.html

http://martinfowler.com/articles/injection.
html#ConcludingThoughts

You might also like