📜 ⬆️ ⬇️

DI and IoC for beginners, part 3

Continuing the topic of DI / IoC using Unity (part 1 , 2 ), we will look at how you can use it in situations where the object is not created by us, and also look at the application of the framework for unit testing.

The situation in which several mappings of the same type are registered can be controlled when it is necessary to transfer all registered types of services (that is, IService[] ). But what if you need to get one specific service from a container? To do this, Unity provides the ability to give objects names. For example, to implement an analogue of this code here.
var svc10 = new MyService(10);<br/>
var svc15 = new MyService(15);<br/>
you need to register just the “nominal” mappings, or rather:
var uc = new UnityContainer();<br/>
//
uc.RegisterType<IService, MyService>( "ten" ,<br/>
new InjectionConstructor( new InjectionParameter(10)))<br/>
.RegisterType<IService, MyService>( "fifteen" ,<br/>
new InjectionConstructor( new InjectionParameter(15)));<br/>
//
var _10 = uc.Resolve<IService>( "ten" );<br/>
var _15 = uc.Resolve<IService>( "fifteen" );<br/>
Also, it is possible to get all registered mappings. This is done using the ResolveAll() function:
foreach (IService svc in uc.ResolveAll<IService>() )<br/>
svc.DoSomething();<br/>

External creation


There are situations when an object for which DI needs to be created is created out of our control. There are a lot of examples where this happens - these are WCF, WPF, various remoting / SoA scripts. Despite the fact that the object is made by someone else, it is still a good idea to be able to connect it (as well as dependent objects) to the overall process.

Here is another synthetic example:
public class HelperClass<br/>
{<br/>
public void DoSomething()<br/>
{<br/>
Console.WriteLine( "Doing something" );<br/>
}<br/>
}<br/>
public class SomeService<br/>
{<br/>
[Dependency]<br/>
public HelperClass MyHelperClass { get; set; }<br/>
public void Go() { MyHelperClass.DoSomething(); }<br/>
}<br/>
<br/>
â‹®<br/>
<br/>
var uc = new UnityContainer();<br/>
var ss = Activator.CreateInstance<SomeService>();<br/>
Console.WriteLine(ss.MyHelperClass == null ); // True
Having received the object as ready, the DI mechanism was not applied, but this is not a problem - it can be obtained post-factum using the BuildUp() :
var uc = new UnityContainer();<br/>
// ""
var ss = Activator.CreateInstance<SomeService>();<br/>
Console.WriteLine(ss.MyHelperClass == null ); // True
var ss2 = uc.BuildUp<SomeService>(ss) ;<br/>
Console.WriteLine(ss2.MyHelperClass == null ); // False
ss2.Go();<br/>
Console.WriteLine(ReferenceEquals(ss, ss2)); // True
As you can see from the code, the BuildUp() method “builds” on an existing object those dependencies that would have been created if the object would have issued an IoC container. Our example also demonstrates that when a new object is added, it is not created — the old one is used.

Of course, if an object is created externally, the behavior of the Resolve() method changes. In particular, if our service implements IService , then we can still do a mapping from IService to an existing object, and later issue the same copy without any problems. To do this, we register an existing object using the RegisterInstance() method:
var uc = new UnityContainer();<br/>
var ss = Activator.CreateInstance<SomeService>();<br/>
uc.BuildUp<SomeService>(ss);<br/>
// IService ss
uc.RegisterInstance<IService>(ss); <br/>
var svc = uc.Resolve<IService>();<br/>
svc.Go();<br/>

Testing


It is from the goals of the mechanism of DI - to simplify unit testing. The bottom line is that for most developers, unit ≡ class, and therefore other classes in the tests need to be replaced. Take another simple example:
public interface IAdder<br/>
{<br/>
int Add( int first, int second);<br/>
}<br/>
public sealed class AdderService : IAdder<br/>
{<br/>
public int Add( int first, int second)<br/>
{<br/>
return first + second;<br/>
}<br/>
}<br/>
public class MyApp<br/>
{<br/>
private IAdder adder;<br/>
public MyApp(IAdder adder)<br/>
{<br/>
this .adder = adder;<br/>
}<br/>
public int AddAndMultiply( int first, int second, int third)<br/>
{<br/>
return adder.Add(first, second) * third;<br/>
}<br/>
}<br/>
The following test is an integration test, since specific copies of two classes are involved in it - AdderService and MyApp :
[Test]<br/>
public void NotAUnitTest()<br/>
{<br/>
var uc = new UnityContainer();<br/>
uc.RegisterType<IAdder, AdderService>();<br/>
Assert.AreEqual(20, uc.Resolve<MyApp>().AddAndMultiply(2, 3, 4));<br/>
}<br/>
In the working code, by the way, the container would be configured exactly as shown above. But when testing, we can replace AdderService different, “test” implementation. The easiest option is to write some FakeAdderService which simply returns a constant:
public class FakeAdderService : IAdder<br/>
{<br/>
public int Add( int first, int second)<br/>
{<br/>
return 5; // !
}<br/>
}<br/>
Now we change the container and - hurray! - we have a full unit test:
[Test]<br/>
public void UnitTestWithFake()<br/>
{<br/>
var uc = new UnityContainer();<br/>
uc.RegisterType<IAdder, FakeAdderService >();<br/>
Assert.AreEqual(20, uc.Resolve<MyApp>().AddAndMultiply(2, 3, 4));<br/>
}<br/>
Of course, our fake - on a particularly flexible - because it returns exactly the value that we expect when you enter (that is, in the test we write “2, 3” and in fake we return the number 5). And if suddenly we need to use other values ​​in another test, what to do then? Make another fake object? Or do we make our fake parameterizable and configure its parameters in a container?
')
Fortunately, nothing like this is needed. Instead, you can use the mock framework, such as Rhino Mocks or Typemock Isolator. Then, without our own fake classes, we can rewrite our test so that a fake IService object is automatically substituted into it, which would return exactly the value we need:
var uc = new UnityContainer();<br/>
var svc = Isolate.Fake.Instance<AdderService>();<br/>
Isolate.WhenCalled(() => svc.Add(0, 0)).WillReturn(5);<br/>
uc.RegisterInstance<IAdder>(svc);<br/>
Assert.AreEqual(20, uc.Resolve<MyApp>().AddAndMultiply(2, 3, 4));<br/>
Above, I created a fake object using Typemock (you can read about it here ), set the behavior for calling the Add() method (always return 5), and added it to the container. Thus, he received a complete, easily configurable unit test.

That's all for now. Automocking and other interesting discuss next time.

Source: https://habr.com/ru/post/63568/


All Articles