7

Is it true that mockito can't mock objects that were already enhanced by CGLIB?

public class Article {

     @Autowired
     private dbRequestHandler

     @Autowired
     private filesystemRequestHandler

     @Transactional
     public ArticleDTO getArticleContents() {

         //extractText() and then save the data in DTO
         //extractImages() and then save the data in DTO
         // some other calls to other databases to save data in dto

       return articleDTO;

     }
     public void extractText() {

        //call to DB

   }

   public void extractImages() {

        // call to file system

   }
}


public class IntegrationTest {

  @Autowired
  private Article article;

  //setup method {

  articleMock = Mockito.spy(article);

  doNothing().when(articleMock).extractImages();
 }
}

In the above example when it comes to doNothing().when(articleMock).extractImages(); it actually calls the real function. On a closer look articleMock gets enhanced two times. One cause of autowiring and second time cause of spying.

If I can't spy on enhaced objects, then how can I test the getArticle() method in my Integration test, so that I can verify a proper DTO is returned.

Note : I actually don't want to test the method which does filesystem calls. just the DB ones. thats why I need to test the getArticle method.

samach
  • 3,244
  • 10
  • 42
  • 54
  • Following what I found of [documentation](http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#doNothing()) I dont imitatly see the problem. Have you tried it by createing the `Article` yourself, and not have it autowired(or at least verified the correctness after autowiring)? – atomman Nov 04 '13 at 14:28
  • yes if I create the `Article` myself, I am able to spy. But I have to autowire it, as in my application every object is created by autowiring, and if I initiate the `Article` by myself then the field in Article class are null (eg the reqHandler objects). If I also initiate the those fields then the fields in those classes are null and the chain goes on.. – samach Nov 04 '13 at 15:04
  • The filtered code and your question(s) don't fit - `getArticle()` in question, `getArticleContents()` in code - it is causing some confusion. You should consider providing some more `IntegrationTest` code. – Cebence Nov 05 '13 at 15:00
  • Actually, we need more of your `IntegrationTest` code in order to understand what is going on. – benzonico Nov 06 '13 at 15:54

4 Answers4

3

If I understand correctly your class is wired by Spring. Spring uses CGLIB to ensure transactional behaviour only if there is no interface, which is implemented by your object. If there is an interface, it uses simple JDK Dynamic Proxies. (see http://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html)

Maybe you could try to extract an interface, and let Spring to use dynamic proxies. Maybe then Mockito could perform better.

Gábor Lipták
  • 9,646
  • 2
  • 59
  • 113
1

If you run as a true unit test and not as an integration test, you need not run in a container having Spring autowire for you. In one of your comments, I think you alluded to trying this, and you noted that there was an endless set of chained object references which you would have to provide as well. But there is a way around that. Mockito provides some predefined Answer classes that you can initialize your mock with. You may want to look at RETURNS_DEEP_STUBS, which will possibly get you around this problem.

Kevin Welker
  • 7,719
  • 1
  • 40
  • 56
1

Will you please update your question with ready-to-go compilable code. Here's some code review suggestions:

Issues with this question code:

  • Article.java missing import: org.springframework.beans.factory.annotation.Autowired
  • Article.java missing import: org.springframework.transaction.annotation.Transactional
  • Article.java attribute syntax issue: dbRequestHandler
  • Article.java attribute syntax issue: filesystemRequestHandler
  • Article.java method has no initialized return statement: articleDTO

Here's what you maybe should use as you questionCode with the above issues fixed:

Article.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

public class Article {

    @Autowired
    private Object dbRequestHandler;

    @Autowired
    private Object filesystemRequestHandler;

    @Transactional
    public ArticleDTO getArticleContents() {

        // extractText() and then save the data in DTO
        // extractImages() and then save the data in DTO
        // some other calls to other databases to save data in dto

        ArticleDTO articleDTO = null;
        return articleDTO;

    }

    public void extractText() {

        // call to DB

    }

    public void extractImages() {

        // call to file system

    }
}

IntegrationTest.java is a poor name for a testClass because it's to generic. I would suggest ArticleTest for a java unit test.

ArticleTest.java

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.annotation.Autowired;

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassWithPrivate.class)
public class ArticleTest {

    @InjectMocks
    private Article cut;

    @Mock
    private Object dbRequestHandler;

    @Mock
    private Object filesystemRequestHandler;

    @Test
    public void testeExtractImages() {

        /* Initialization */
        Article articleMock = Mockito.spy(cut);

        /* Mock Setup */
        Mockito.doNothing().when(articleMock).extractImages();

        /* Test Method */
        ArticleDTO result = cut.getArticleContents();

        /* Asserts */
        Assert.assertNull(result);

    }

}
javaPlease42
  • 4,699
  • 7
  • 36
  • 65
1

You can utilize AdditionalAnswers.delegatesTo method. In following example, the secondProxyDoingMocking declaration creates something like a spy (compare with implementation of spy() method) except it uses "lightweight" method delegation.

import org.mockito.AdditionalAnswers;

public class ArticleTest {

    @Autowired
    private Article firstProxyDoingAutowiring;

    @Test
    public void testExtractImages() {
        Article secondProxyDoingMocking = Mockito.mock(Article.class,
                Mockito.withSettings().defaultAnswer(
                        AdditionalAnswers.delegatesTo(firstProxyDoingAutowiring)
                )
        );
        Mockito.doNothing().when(secondProxyDoingMocking).extractImages();
        ...
    }

}

I didn't test this example, however I assembled it from my working code. My use case was similar: return constant value for given method, call real method for all remaining methods of Spring @Transactional-annotated bean.

Tomáš Záluský
  • 10,735
  • 2
  • 36
  • 64