How to inject mock deep into class hierarchy

1,181 views
Skip to first unread message

Russell Bateman

unread,
Aug 11, 2014, 8:03:49 PM8/11/14
to moc...@googlegroups.com
(I've tried to simplify the class relationships for the illustration.)

I need to understand how, from TestQueueConfiguration, to inject a mock that returns a pre-hatched hash map with the values I want and avoid actually calling into MyProperties to get them.

Test QueueConfiguration such that mocked myProperties get called instead of actual MyProperties. How to inject this mock deep into QueueConfiguration -> QueueObjectManager -> QueueObject?

Many thanks for comments.


public class TestQueueConfiguration
{
    private QueueConfiguration configuration;   // class under test
    @Mock private MyProperties  myProperties;    // class we mock to avoid database

    public void setUp()
    {
        MockitoAnnotations.initMocks( this );
    }

    @Test
    public void test()
    {
        Map< String, Attribute > attributes = new HashMap< String, Attribute >(1);
        attributes.put( "key", new Attribute( "value" ) );

        when( myProperties.getAttributes() ).thenReturn( attributes );

        configuration = new QueueConfiguration();

        Map< String, String > result = configuration.getQueueProperties( 1 );  <---- MyProperties down under here
        assertEquals( result.size(), 1 );
    }
}

// - class under test --------------------------------------------------------------
public class QueueConfiguration
{
    public QueueConfiguration() { }

    Map< String, String > getQueueProperties( int queueId )
    {
        QueueObjectManager             qom = new QueueObjectManager( queueId, "foo.conf" );
        QueueObjectManager.QueueObject qO  = qom.getQueueObjectByQueueId( queueId );

        Map< String, String > result = new HashMap< String, String >();

        if( qO.queueId == 1 )
            result.putAll( qO.readPropertiesFromFile() );    <---- MyProperties down under here

        return result;
    }
}

// - class I want to mock ----------------------------------------------------------
public class MyProperties
{
    Map< String, Attribute > attributes = new HashMap< String, Attribute >();

    public MyProperties( Map< String, Attribute > map )
    {
        attributes = map;
    }

    public Map< String, Attribute > getAttributes()
    {
        // pretend this has gone to the database for these attributes...
        return attributes;
    }
}


// - class used by class under test inside which I want MyProperties mocked --------
public class QueueObjectManager
{
    private QueueObject queueObject;

    public QueueObjectManager( int queueId, String filename )
    {
        this.queueObject = new QueueObject( queueId, filename );
    }

    public QueueObjectManager.QueueObject getQueueObjectByQueueId( int queueId )
    {
        QueueObject queue = queueObject;

        if( queue.queueId == queueId )
            return queue;

        throw new NoSuchElementException( "Queue identified does not exist" );
    }


    // - subclass where MyProperties is actually used ---------
    class QueueObject
    {
        protected int    queueId;
        protected String filename;

        public QueueObject( int queueId, String filename )
        {
            this.filename = filename;
            this.queueId  = queueId;
        }

        // - method where MyProperties is used ------------------
        public Map< String, String > readPropertiesFromFile()
        {
            Map< String, String > result = new HashMap< String, String >();

            try
            {
                MyProperties props = new ObjectRequester().getSecureObject( filename );
                Map< String, Attribute > attributeMap = props.getAttributes();

                for( Map.Entry< String, Attribute > map : attributeMap.entrySet() )
                {
                    String key   = map.getKey();
                    String value = getValue( map.getValue() );
                    result.put( key, value );
                }
            }
            catch( Exception e )
            {
                ;
            }

            return result;
        }

        private String getValue( Attribute attribute )
        {
            return attribute.getValue();
        }
    }
}

Tian

unread,
Aug 12, 2014, 12:14:41 PM8/12/14
to moc...@googlegroups.com
Hi,
I think the problems are:
1. the method "getQueueProperties" is doing to much work here. It's a violation of single responsibility principle.
This method shouldn't take responsibility for creating QueueObjectManager.

2. And QueueConfiguration has hard dependency to QueueObjectManager. But QueueObjectManager it the object you want to isolate (mock out).
So you have to remove this hard dependency, by injecting one for example.

Your question is about mock MyProperties. But you want mock MyProperties only because It's depended by QueueObject which is depended by QueueObjectManager which depended by QueueConfiguration. When you want to test QueueConfiguration you just have to isolate his dependency, QueueObjectManager  in this case, instead of isolate his dependency's dependency.

Brice Dutheil

unread,
Aug 12, 2014, 12:23:13 PM8/12/14
to moc...@googlegroups.com
Hey,

Also if you want Mockito to do that, then no it's not possible, it's not the job of Mockito to perform complex injections.
Actually if you really need to configure your object you may want to create builders that can configure these objects, to be able to use preconfigured objects or mocks.

And I agree with Tian, you may want to reduce the responsibilities of the `getQueueProperties`.




-- Brice


--
You received this message because you are subscribed to the Google Groups "mockito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mockito+u...@googlegroups.com.
To post to this group, send email to moc...@googlegroups.com.
Visit this group at http://groups.google.com/group/mockito.
For more options, visit https://groups.google.com/d/optout.

Russell Bateman

unread,
Aug 12, 2014, 12:31:26 PM8/12/14
to moc...@googlegroups.com
Thank you for your comments. While I agree with your assessment, and even originally rewrote this code, alas, the rewrite was refused and I'm stuck having to hack a (an ugly) way around it. Perhaps I'll just not offer unit-level testing of this functionality.

Best regards, my friends.

Russ

Brice Dutheil

unread,
Aug 12, 2014, 12:42:35 PM8/12/14
to moc...@googlegroups.com
Too bad. Sometime an intermediary integration test can do the job.

Good luck anyway :)

-- Brice
Reply all
Reply to author
Forward
0 new messages