Discussions

News: Dependency Injection for Unit Tests

  1. Dependency Injection for Unit Tests (26 messages)

    Are you bitten by the dependency injection bug? Do you get irritated if you ever have to do the injecting yourself? Ara Abrahamian got annoyed at having to do so in his unit tests, and decided to write some code so he could use D.I. in all of his test cases.
    Ever wanted Dependency Injection for your unit tests? I got sick of doing context.getBean("thisDao") and context.getBean("thatDao") when testing my springified DAO classes.

    What I wanted was something like this:

    public class TestUserManagement extends BaseTestCase {
        private UserDao userDao = null;

        public void testSomething() throws Exception {
            userDao.createAdmin();
        }
    }

    Note that I don't new any UserDao. I expect the DI container to instantiate it for me.
    Dependency Injection for Unit Tests

    Threaded Messages (26)

  2. Say no to this.[ Go to top ]

    It sounds like a bad practice which can cause some problems, especially for projects that are using complete J2EE containers. I've posted some thoughts on this in my blog.
  3. PicoUnit to be released soon[ Go to top ]

    I'm just about to release a testing framework based on PicoContainer: PicoUnit, it's a replacement for JUnit (tsk, tsk), but I'm going to do my best to achieve complete compatability with JUnit.

    Here's how your example would look in PicoUnit:

    public class UserDaoTest implements Test /*marker interface*/ {
      private final UserDao userDao;

      public UserDaoTest(UserDao userDao) {
        this.userDao = userDao;
      }

      public void testSomething() throws Throwable {
        userDao.createAdmin();
      }
    }


    Here's an example using a MockFactory class to wrap EasyMock

    // Classic Pico example
    public class GirlTest implements Test {
      private final Boy boy;

      public KissingTest(MockFactory mockFactory) {
        this.mockFactory = mockFactory;
        this.boy = (Boy) mockFactory.createStrick(Boy.class);
      }

      public void testGirlsKissBoys(Verify verify) {
        boy.kiss();

        mockFactory.replay();

        Girl girl = new Girl(boy);
        girl.kiss();

        verify.that(1, girl.getKissCount());
      }
    }


    I've taken this a bit further:

    public class GirlTest implements Test {
      // Mock
      private Boy boy;

      // This method will be 'injected' with mocks
      public mock(Boy boy) {
        this.boy = boy;
      }

      public void testGirlsKissBoys(MockFactory mockFactory, Verify verify) {
        boy.kiss();

        mockFactory.replay();

        Girl girl = new Girl(boy);
        girl.kiss();

        verify.that(1, girl.getKissCount());
      }
    }
  4. The link's broken, so I can't read the article (not without Googling, anyway).

    I think unit testing in general should have nothing to do with the container. Simple JUnit tests, no IoC container in sight.

    A certain amount of integration testing can often be done outside an application server, but using an IoC container. That's where this approach may make sense. I often use an abstract base test case to instantiate the IoC container in this case. But it's not unit testing. Unit testing is about testing in isolation--from other objects, any form of container...

    Rgds
    Rod
  5. OK, I can see it now. While I don't think this is a unit testing technique, it's a seriously cool idea for integration testing. I like it.

    R
  6. I like it too. I modified it a bit and moved the initializing code to an intitializer block. That way I don't have to remember to make a call to super if I override setUp. Works great for live integration tests of database access code.

    Thomas
  7. I like it too. I modified it a bit and moved the initializing code to an intitializer block. That way I don't have to remember to make a call to super if I override setUp. Works great for live integration tests of database access code.Thomas
    Really bad idea for performance reason. All heavy initialization MUST be in setUp() method because test runers are creating instance for all test cases, so your code will be executed during construction time and will not be recycled untill all testcases finished.
  8. I like it too. I modified it a bit and moved the initializing code to an intitializer block. That way I don't have to remember to make a call to super if I override setUp. Works great for live integration tests of database access code.Thomas
    Really bad idea for performance reason. All heavy initialization MUST be in setUp() method because test runers are creating instance for all test cases, so your code will be executed during construction time and will not be recycled untill all testcases finished.
    This won't make any difference since setUp() is invoked before each testXXX() method.

    --
    Cedric
  9. I like it too. I modified it a bit and moved the initializing code to an intitializer block. That way I don't have to remember to make a call to super if I override setUp. Works great for live integration tests of database access code.Thomas
    Really bad idea for performance reason. All heavy initialization MUST be in setUp() method because test runers are creating instance for all test cases, so your code will be executed during construction time and will not be recycled untill all testcases finished.
    This won't make any difference since setUp() is invoked before each testXXX() method.-- Cedric
    It might make a difference for big tests - looks like each testXXX() method is loaded separately up front - so all the initializers would run first - hogging memory. The setUp is run before each testXXX() method and the memory could be recycled quicker.

    Here is the sequence for a two method test:
    Init
    Init
    setUp
    Test1
    setUp
    Test2

    Thomas
  10. It might make a difference for big tests - looks like each testXXX() method is loaded separately up front - so all the initializers would run first - hogging memory. The setUp is run before each testXXX() method and the memory could be recycled quicker.Here is the sequence for a two method test:InitInitsetUpTest1setUpTest2Thomas
    Nope, that's not how it works because of a bug in JUnit which makes it instantiate your entire test class before *each* method invocation.

    I documented this bug here.

    Yup, it's really an incredible bug.

    --
    Cedric
    http://beust.com/weblog
  11. It might make a difference for big tests - looks like each testXXX() method is loaded separately up front - so all the initializers would run first - hogging memory. The setUp is run before each testXXX() method and the memory could be recycled quicker.Here is the sequence for a two method test:InitInitsetUpTest1setUpTest2Thomas
    Nope, that's not how it works because of a bug in JUnit which makes it instantiate your entire test class before *each* method invocation.I documented this bug here.Yup, it's really an incredible bug.-- Cedrichttp://beust.com/weblog
    Cedric, this is a feature. :-)

    Test runner has to instantiate classes, because they could also came from TestSuite's (that suite()). That is the reason why initialization has to be done in setUp() method, but not in the test constructor or class or static initializer. That was design choice.
  12. Cedric, this is a feature. :-)
    Do you have a proof a that? Like in the documentation or an explanation from the authors?

    Because I can't for the life of me understand why it is so.
    Test runner has to instantiate classes, because they could also came from TestSuite's (that suite()). That is the reason why initialization has to be done in setUp() method, but not in the test constructor or class or static initializer. That was design choice.
    Sorry, I still don't understand.

    Let's take the problem backwards.

    Can you show me an example where the test class is instantiated *once* (what I think is the correct implementation) and where the behavior of the tests is going to be wrong?

    --
    Cedric
  13. I like it too. I modified it a bit and moved the initializing code to an intitializer block. That way I don't have to remember to make a call to super if I override setUp. Works great for live integration tests of database access code.Thomas
    Really bad idea for performance reason. All heavy initialization MUST be in setUp() method because test runers are creating instance for all test cases, so your code will be executed during construction time and will not be recycled untill all testcases finished.
    This won't make any difference since setUp() is invoked before each testXXX() method.-- Cedric
    Cedric, I'm aware of your concerns regarding setUp() method. :-)

    However original test runner is using new classloader to run each test. Potentially these classloaders could be dropped/recycled once test is finished/failed. So, when initialization done in setUp() method memory consumption will be quite less, especially for a large test suites.
  14. Dion,

    Can you repost with the link please, the markup is a bit buggered...

    Cheers
    N
  15. http://jroller.com/page/ara_e/20040717#dependency_injection_for_unit_tests
  16. I've used a slightly different approach in a small side-project.

    My approach exposes the container, which I now think is a bad idea. One nice thing about what I've done, and which has helped me a lot in practise, is to automatically pick up the application context files using a simple naming convention.

    This way I can specify an abstract test case for my interfaces, and simply extend that test case for each implementation, adding specific tests as needed. So, as you inherit basic test methods, you also inherit the basic dependencies too.

    I wrote about it in more detail and Dmitriy Kopylenko posted it for me;
    Spring-enabled testcase on the Spring Confluence Wiki.

    There's definitely a case for using something like EasyMock in unit tests, and I've probably over-used the concept described here, but it does have a place.
  17. All that mess for not having to use this?

    bean1 = context.getBean("bean1");
    bean2 = context.getBean("bean2");
    bean3 = context.getBean("bean3");
    bean4 = context.getBean("bean4");

    That's what I use IDEs for...
  18. What's useful about dependency injection for me is how the tests show me exactly how to create an object and how to create its dependencies.

    The tests also point out smells such as when an object has too many dependencies. If setup is becoming tedious, that would be an indicator that the design could be improved to give objects more defined responsibilities.

    I would not try to cover this over by allowing a container to auto-wire it - at least not in the tests as they are important design guides.
  19. Joe, in integration tests you normally have more than 1 dependency, after all it's about integration :-) I don't see any value in keeping the dependencies defined by more labor work. If the design is bad, it's far easier to see in regular unit tests. If a unit test needs a lot of mocks I'm sure jmock's or easymock's long and verbose setups are a much better indicator of bad design.

    Ara.
  20. Easymock + jmock[ Go to top ]

    Btw Joe, I can't wait for the Easymockish JMock replay API :-) It's cool, we need it! Any progress on it (http://jira.codehaus.org/browse/JMOCK-32)?

    Ara.
  21. Several people want this and will be willing to work on it. The best thing to do, if you want it to happen, is to join the jMock user and dev mailing lists and get something organised.
  22. Thanks Ara,

    That has cleared things up. For integration and acceptance tests I can see this as a very useful thing, although usually I fire up my real container for these (i.e. jetty, pico, spring, etc).

    I think you should make it clearer that this is a technique for integration tests rather than unit tests, otherwise readers may get the wrong idea.

    -Joe

    EasyMock wrapper to jMock is coming soon.
  23. public class BaseTestCase extends TestCase {
        protected ApplicationContext context;

        public TestUserManagement() {
            context = new ClassPathXmlApplicationContext(new String[]{
                "/testApplicationContext.xml",
                "/otherContext.xml"});
        }
       
    }

    ------------------------------------------

    public class TestUserManagement extends BaseTestCase {
        private UserDao userDao = null;



        public void testSomething() throws Exception {
            userDao.createAdmin();
        }
    }
  24. public class BaseTestCase extends TestCase {    protected ApplicationContext context;    public TestUserManagement() {        context = new ClassPathXmlApplicationContext(new String[]{            "/testApplicationContext.xml",            "/otherContext.xml"});    }   }
    The reason I create the context object in setUp is to get a totally fresh set of components back. So when setUp() is done I have a fresh HQL in-memory database, then a db related test is run, and when the next test is about to run, it in turn gets a fresh db too because the context is created again. So I don't have any cleanup code between my tests, and I'm sure no data is mistakenly left behind when a test finishes. After all tests shouldn't depend on each other, right? It's slower but safer.

    Ara.
  25. Your right, but DBunit provides a simple way to reInitialize your database for each test. With this tool you can make your tests against your real database engine.
  26. I'm not sure why you're bothering to wire this yourself. In the example you've show, you're constructing a ClassPathXmlApplicationContext. The following would do all the work for you:


    ClassPathXmlApplicationContext context = ...;

    context.getBeanFactory().autowireBeanProperties(
                        this,
                        autowireMode,
                        false);


    where "autowireMode" is one of the contstants from
    AutowireCapableBeanFactory.
  27. Why not use nomething like this ?


    public class BaseTestCase extends TestCase {
        protected ApplicationContext context;

        public TestUserManagement() {
            context = new ClassPathXmlApplicationContext(new String[]{
                "/testApplicationContext.xml",
                "/otherContext.xml"});
        }
       
    }

    ------------------------------------------

    public class TestUserDao extends BaseTestCase {
        private UserDao userDao;

        public TestUserManagement() {
            super();
            this.userDao = this.context.getBean("userDao");
        }

        public void testSomething() throws Exception {
            this.userDao.createAdmin();
        }

    }

    --------------------------------------

    Just copy your real XML applicationContext in the Junit classpath and modify it to use local datasource.
    This way, your applicationContext is initalized only once. And you test your applicationContext config too.

    Initializing your applicationContext in the setUp() method make the test too slow (initialize context for each xxxTest method) specialy if you use Hibernate (it initializes Hibernate too).