Unit Testing 101: Inversion Of Control
Join the DZone community and get the full member experience.
Join For Freeinversion of control is one of the most common and widely used techniques for handling class dependencies in software development and could easily be the most important practice in unit testing. basically, it determines if your code is unit-testable or not. not just that, but it can also help improve significantly your overall software structure and design. but what is it all about? it is really that important? hopefully we’ll clear those out on the following lines.
identifying class dependencies
as we mentioned before, inversion of control is a technique used to handle class dependencies effectively; but, what exactly is a dependency ? in real life, for instance, a car needs an engine in order to function; without it, it probably won’t work at all. in programming it is the same thing; when a class needs another one in order to function properly, it has a dependency on it. this is called a class dependency or coupling .
let’s look at the following code example:
public class usermanager { private md5passwordhasher passwordhasher; public usermanager() { this.passwordhasher = new md5passwordhasher(); } public void resetpassword(string username, string password) { // get the user from the database user user = datacontext.users.getbyname(username); string hashedpassword = this.passwordhasher.hash(password); // set the user new password user.password = hashedpassword; // save the user back to the database. datacontext.users.update(user); datacontext.commit(); } // more methods... } public class md5passwordhasher { public string hash(string plaintextpassword) { // hash password using an encryption algorithm... } }
the previous code describes two classes,
usermanager
and
passwordhasher
. we can see how
usermanager
class initializes a new instance of the
passwordhasher
class on its constructor and keeps it as a class-level variable so all methods in the class can use it (line 3). the method we are going to focus on is the
resetpassword
method. as you might have already noticed, the line 15 is highlighted. this line makes use of the
passwordhasher
instance, hence, marking a strong class dependency between
usermanager
and
passwordhasher
.
don’t call us, we’ll call you
when a class creates instances of its dependencies, it knows what implementation of that dependency is using and probably how it works. the class is the one controlling its own behavior. by using inversion of control, anyone using that class can specify the concrete implementation of each of the dependencies used by it; this time the class user is the one partially controlling the class behavior (or how it behaves on the parts where it uses those provided dependencies).
anyways, all of this is quite confusing. let’s look at an example:
public class usermanager { private ipasswordhasher passwordhasher; public usermanager(ipasswordhasher passwordhasher) { this.passwordhasher = passwordhasher; } public void resetpassword(string username, string password) { // get the user from the database user user = datacontext.users.getbyname(username); string hashedpassword = this.passwordhasher.hash(password); // set the user new password user.password = hashedpassword; // save the user back to the database. datacontext.users.update(user); datacontext.commit(); } // more methods... } public interface ipasswordhasher { string hash(string plaintextpassword); } public class md5passwordhasher : ipasswordhasher { public string hash(string plaintextpassword) { // hash password using an encryption algorithm... } }
inversion of control is usually implemented by applying a design pattern called the strategy pattern (as defined in the gang of four book). this pattern consists on abstracting concrete component and algorithm implementations from the rest of the classes by exposing only an interface they can use; thus making implementations interchangeable at runtime and encapsulate how these implementations work since any class using them should not care about how they work.
so, in order to achieve this, we need to sort some things out:
-
abstract an interface from the
md5passwordhasher
class,ipasswordhasher
; so anyone can write custom implementations of password hashers (line 28-31). -
mark the
md5passwordhasher
class as an implementation of theipasswordhasher
interface (line 33). -
change the type of the password hasher used by
usermanager
toipasswordhasher
(line 3). -
add a new constructor parameter of type
ipasswordhasher
interface (line 5), which is the instance theusermanager
class will use to hash its passwords. this way we delegate the creation of dependencies to the user of the class and allows the user to provide any implementation it wants, allowing it to control how the password is going to be hashed.
this is the very essence of inversion of control: minimize class coupling. the user of the
usermanager
class has now control over how passwords are hashed. password hashing control has been inverted from the class to the user. here is an example on how we can specify the only dependency of the
usermanager
class:
ipasswordhasher md5passwordhasher = new md5passwordhasher(); usermanager usermanager = new usermanager(md5passwordhasher); usermanager.resetpassword("luis.aguilar", "12345");
so, why is this useful? well, we can go crazy and create our own hasher implementation to be used by the usermanager class:
// plain text password hasher: public class plaintextpasswordhasher : ipasswordhasher { public string hash(string plaintextpassword) { // let's disable password hashing by returning // the plain text password. return plaintextpassword; } } // usage: ipasswordhasher plaintextpasswordhasher = new plaintextpasswordhasher(); usermanager usermanager = new usermanager(plaintextpasswordhasher); // resulting password will be: 12345. usermanager.resetpassword("luis.aguilar", "12345");
conclusion
so, this concludes our article on inversion of control. hopefully with a little more practice, you will be able to start applying this to your code. of course, the biggest benefit of this technique is related to unit testing. so, what does it has to do with unit testing? well, we’re going to see this when we get into type mocking . so, stay tuned!
Published at DZone with permission of Luis Aguilar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments