1#!/usr/bin/perl 2 3use strict; 4use warnings; 5 6use Test::More tests => 65; 7use Test::Exception; 8 9BEGIN { 10 use_ok('IOC::Container'); 11 use_ok('IOC::Service::SetterInjection'); 12} 13 14# Cyclical dependencies: 15# +---+ 16# +--| A |<-+ 17# | +---+ | 18# | +---+ | 19# +->| B |--+ 20# +---+ 21 22{ 23 { 24 package A; 25 sub new { bless { b => $_[1] }, $_[0] } 26 sub testA { 'testA' } 27 28 package B; 29 sub new { bless { a => $_[1] }, $_[0] } 30 sub testB { 'testB' } 31 } 32 33 my $container = IOC::Container->new(); 34 $container->register(IOC::Service->new('a' => sub { A->new((shift)->get('b')) })); 35 $container->register(IOC::Service->new('b' => sub { B->new((shift)->get('a')) })); 36 37 my $a; 38 lives_ok { 39 $a = $container->get('a'); 40 } '... we got our A object ok'; 41 isa_ok($a, 'A'); 42 43 isa_ok($a->{b}, 'B'); 44 is(ref($a->{b}), 'B', '... and it is actually a B object too'); 45 is($a->{b}->testB(), 'testB', '... make sure our B object works as expected'); 46 47 isa_ok($a->{b}->{a}, 'A'); 48 is(ref($a->{b}->{a}), 'IOC::Service::Deferred', '... but this it is actually a IOC::Service::Deferred object'); 49 50 is($a->{b}->{a}->testA(), 'testA', '... this should inflate our deferred A object'); 51 is(ref($a->{b}->{a}), 'A', '... now this should be an A object'); 52 53 is($a, $a->{b}->{a}, '... and our A instances are both the same since they are singletons'); 54} 55 56# Graph Dependecies 57# +---+ 58# +--| C |<-+ 59# | +---+ | 60# +-V-+ +---+ 61# | D | | F | 62# +---+ +-^-+ 63# | +---+ | 64# +->| E |--+ 65# +---+ 66 67{ 68 69 { 70 package C; 71 sub new { bless { d => $_[1] }, $_[0] } 72 73 package D; 74 sub new { bless { e => $_[1] }, $_[0] } 75 76 package E; 77 sub new { bless { f => $_[1] }, $_[0] } 78 79 package F; 80 sub new { bless { c => $_[1] }, $_[0] } 81 } 82 83 my $container = IOC::Container->new(); 84 $container->register(IOC::Service->new('c' => sub { C->new((shift)->get('d')) })); 85 $container->register(IOC::Service->new('d' => sub { D->new((shift)->get('e')) })); 86 $container->register(IOC::Service->new('e' => sub { E->new((shift)->get('f')) })); 87 $container->register(IOC::Service->new('f' => sub { F->new((shift)->get('c')) })); 88 89 my $c; 90 lives_ok { 91 $c = $container->get('c'); 92 } '... we got our C object ok'; 93 isa_ok($c, 'C'); 94 95 isa_ok($c->{d}, 'D'); 96 is(ref($c->{d}), 'D', '... and it is actually a D object too'); 97 98 isa_ok($c->{d}->{e}, 'E'); 99 is(ref($c->{d}->{e}), 'E', '... and it is actually a E object too'); 100 101 isa_ok($c->{d}->{e}->{f}, 'F'); 102 is(ref($c->{d}->{e}->{f}), 'F', '... and it is actually a F object too'); 103 104 isa_ok($c->{d}->{e}->{f}->{c}, 'C'); 105 is(ref($c->{d}->{e}->{f}->{c}), 'IOC::Service::Deferred', '... however this is actually an IOC::Service::Deferred object'); 106 107 isa_ok($c->{d}->{e}->{f}->{c}->{d}, 'D'); 108 is(ref($c->{d}->{e}->{f}->{c}), 'C', '... but now we have been infalted into a proper C object'); 109} 110 111# Graph Dependecies 112# +---+ 113# +--| G |<-+ 114# | +---+ | 115# +-V-+ +---+ +---+ +---+ 116# | H | | J | | K |->| L | 117# +---+ +-^-+ +-^-+ +---+ 118# | | +---+ | | 119# | +->| I |-+ | 120# | +---+ | 121# +-----------------+ 122 123{ 124 { 125 package G; 126 sub new { bless [ $_[1] ], $_[0] } 127 128 package H; 129 sub new { bless { i => $_[1], k => $_[2] }, $_[0] } 130 131 package I; 132 sub new { bless { j => $_[1] }, $_[0] } 133 134 package J; 135 sub new { bless { g => $_[1] }, $_[0] } 136 137 package K; 138 sub new { bless { l => $_[1] }, $_[0] } 139 } 140 141 my $container = IOC::Container->new(); 142 $container->register(IOC::Service->new('g' => sub { G->new((shift)->get('h')) })); 143 $container->register(IOC::Service->new('h' => sub { H->new($_[0]->get('i'), $_[0]->get('k')) })); 144 $container->register(IOC::Service->new('i' => sub { I->new((shift)->get('j')) })); 145 $container->register(IOC::Service->new('j' => sub { J->new((shift)->get('g')) })); 146 $container->register(IOC::Service->new('k' => sub { K->new((shift)->get('l')) })); 147 $container->register(IOC::Service->new('l' => sub { '... this is the end' })); 148 149 my $g; 150 lives_ok { 151 $g = $container->get('g'); 152 } '... we got our G object ok'; 153 isa_ok($g, 'G'); 154 155 isa_ok($g->[0], 'H'); 156 is(ref($g->[0]), 'H', '... and it is actually a H object too'); 157 158 isa_ok($g->[0]->{i}, 'I'); 159 is(ref($g->[0]->{i}), 'I', '... and it is actually a I object too'); 160 161 isa_ok($g->[0]->{i}->{j}, 'J'); 162 is(ref($g->[0]->{i}->{j}), 'J', '... and it is actually a J object too'); 163 164 isa_ok($g->[0]->{i}->{j}->{g}, 'G'); 165 is(ref($g->[0]->{i}->{j}->{g}), 'IOC::Service::Deferred', '... and it is actually an IOC::Service::Deferred object'); 166 167 isa_ok($g->[0]->{i}->{j}->{g}->[0], 'H'); 168 is(ref($g->[0]->{i}->{j}->{g}), 'G', '... and it is actually the inflated G object now'); 169 170 isa_ok($g->[0]->{k}, 'K'); 171 is(ref($g->[0]->{k}), 'K', '... and it is actually a K object too'); 172 173 is($g->[0]->{k}->{l}, '... this is the end', '... and this is our L string'); 174 175 176} 177 178## EDGE CASES 179 180# Cyclical dependencies: 181# +---+ 182# +--| M |<-+ 183# | +---+ | 184# | > $m->test() 185# | +---+ | 186# +->| N |--+ 187# +---+ 188# 189# in this test we call a method on the deferred M instance 190# before we are finished creating N, this results in M 191# being intialized before N is finished initializing. 192# 193# +---+ +---+ +-------+ / N->new calls \ +-------+ 194# | M |--->| N |-->| <<M>> |-| $m->testM() which |->| <<N>> | 195# +---+ +---+ +-------+ \ inflates <<M>> / +-------+ . 196# \ \ \ V \........| 197# \ \ \.............|__________________________| 198# \ \__________________________________________________| 199# \__________________________________________________________| 200# <<scope of call to instance()>> 201# 202# basically what is happening is that when the first deferred <<M>> 203# is inflated, the first real M has not yet been fully created. Thus 204# the IOC::Service object M occupies first stores the deferred <<M>> 205# then returns, which satisfies the N and returns, which then satisfies 206# the first M which is then stored in the IOC::Service instance that 207# the first <<M>> is stored in, thus overwriting it. 208# 209# We solve this by checking to see if the IOC::Service object in question 210# already has an instance, in which case, we use that one and discard the 211# extra instance. It is not the best solution (since we create an instace 212# only to discard it), but it allows for this to work. 213 214{ 215 { 216 package M; 217 sub new { bless { n => $_[1] }, $_[0] } 218 sub testM { 'testM' } 219 ## uncomment this line to see the 220 ## recursive spiral of death happen. 221 # sub testM { (shift)->{n}->testN() } 222 223 package N; 224 sub new { 225 my ($class, $m) = @_; 226 Test::More::is(ref($m), 'IOC::Service::Deferred', '... we got a deferred M'); 227 Test::More::is($m->testM(), 'testM', '... and we got the right output'); 228 bless { m => $m }, $class 229 } 230 sub testN { 'testN' } 231 } 232 233 my $container = IOC::Container->new(); 234 $container->register(IOC::Service->new('m' => sub { M->new((shift)->get('n')) })); 235 $container->register(IOC::Service->new('n' => sub { N->new((shift)->get('m')) })); 236 237 my $m; 238 lives_ok { 239 $m = $container->get('m'); 240 } '... we got our M object ok'; 241 isa_ok($m, 'M'); 242 243 isa_ok($m->{n}, 'N'); 244 is(ref($m->{n}), 'IOC::Service::Deferred', '... this an IOC::Service::Deferred instance'); 245 is($m->{n}->testN(), 'testN', '... make sure our N object works as expected'); 246 is(ref($m->{n}), 'N', '... this now an N instance'); 247 248 isa_ok($m->{n}->{m}, 'M'); 249 is(ref($m->{n}->{m}), 'M', '... but this it is actually an M object since it was resolved earlier'); 250 251 is($m, $m->{n}->{m}, '... and our M instances are both the same since they are singletons'); 252} 253 254# test some of the errors for this 255 256{ 257 throws_ok { 258 IOC::Service::Deferred->new(); 259 } 'IOC::InsufficientArguments', '... got the error we expected'; 260 261 throws_ok { 262 IOC::Service::Deferred->new([]); 263 } 'IOC::InsufficientArguments', '... got the error we expected'; 264 265 throws_ok { 266 IOC::Service::Deferred->new(bless({}, 'Fail')); 267 } 'IOC::InsufficientArguments', '... got the error we expected'; 268 269} 270 271{ 272 my $container = IOC::Container->new(); 273 $container->register(IOC::Service->new('a' => sub { A->new((shift)->get('b')) })); 274 $container->register(IOC::Service->new('b' => sub { B->new((shift)->get('a')) })); 275 276 my $a; 277 lives_ok { 278 $a = $container->get('a'); 279 } '... we got our A object ok'; 280 isa_ok($a, 'A'); 281 282 isa_ok($a->{b}->{a}, 'A'); 283 is(ref($a->{b}->{a}), 'IOC::Service::Deferred', '... but this it is actually a IOC::Service::Deferred object'); 284 285 ok(!defined($a->{b}->{a}->can('Fail')), '... we dont have this method'); 286 287 is(ref($a->{b}->{a}), 'A', '... now this it is actually a A object'); 288} 289 290{ 291 my $container = IOC::Container->new(); 292 $container->register(IOC::Service->new('a' => sub { A->new((shift)->get('b')) })); 293 $container->register(IOC::Service->new('b' => sub { B->new((shift)->get('a')) })); 294 295 my $a; 296 lives_ok { 297 $a = $container->get('a'); 298 } '... we got our A object ok'; 299 isa_ok($a, 'A'); 300 301 isa_ok($a->{b}->{a}, 'A'); 302 is(ref($a->{b}->{a}), 'IOC::Service::Deferred', '... but this it is actually a IOC::Service::Deferred object'); 303 304 throws_ok { 305 $a->{b}->{a}->Fail() 306 } 'IOC::MethodNotFound', '... got the error we expected'; 307 308 is(ref($a->{b}->{a}), 'A', '... now this it is actually a A object'); 309} 310