模拟CGLIB增强对象

7

Mockito无法mock被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();
 }
}

在上面的示例中,当涉及到doNothing().when(articleMock).extractImages();时,实际上调用了真实的函数。仔细观察可以发现,articleMock被增强了两次。一次是由于autowiring,另一次是由于spying
如果我不能对增强对象进行间谍操作,那么我该如何在我的集成测试中测试getArticle()方法,以便验证返回一个正确的DTO。
注意:我实际上不想测试执行文件系统调用的方法,只想测试数据库调用的方法,因此我需要测试getArticle方法。

根据我在文档中找到的内容,我并没有立即看出问题。你是否尝试过自己创建Article,而不是使用自动装配(或者至少在自动装配后验证正确性)? - atomman
是的,如果我自己创建“Article”,我就能够进行监视。但是我必须进行自动装配,因为在我的应用程序中,每个对象都是通过自动装配创建的,如果我自己初始化“Article”,那么Article类中的字段将为空(例如reqHandler对象)。如果我也初始化这些字段,那么这些类中的字段也将为空,这种情况会一直延续下去... - samach
过滤后的代码和你的问题不匹配 - 问题中是 getArticle(),而代码中是 getArticleContents() - 这会导致一些混淆。你应该考虑提供更多的 IntegrationTest 代码。 - Cebence
实际上,为了理解发生了什么,我们需要更多你的“IntegrationTest”代码。 - benzonico
4个回答

3
如果我理解正确,您的类是由Spring进行连接的。Spring使用CGLIB来确保只有在对象没有实现接口时才有事务行为。如果有接口,则使用简单的JDK动态代理。(请参见http://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html)也许您可以尝试提取一个接口,让Spring使用动态代理。也许这样Mockito能表现得更好。

1
你可以使用AdditionalAnswers.delegatesTo方法。在以下示例中,secondProxyDoingMocking声明创建了类似于spy的东西(与spy()方法的实现进行比较),除了它使用“轻量级”方法委托。
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();
        ...
    }

}

我没有测试过这个例子,但是我从我的工作代码中组装了它。我的用例类似:为给定的方法返回常量值,在Spring @Transactional注释的bean的所有其余方法中调用真实方法。请注意,我只是翻译文本,不会回答问题。

1
请您更新问题并提供可编译的代码。以下是代码审查建议:
问题代码存在以下问题:
  • Article.java缺少导入:org.springframework.beans.factory.annotation.Autowired
  • Article.java缺少导入:org.springframework.transaction.annotation.Transactional
  • Article.java属性语法问题:dbRequestHandler
  • Article.java属性语法问题:filesystemRequestHandler
  • Article.java方法没有初始化返回语句:articleDTO
以下是修复以上问题后建议使用的questionCode:

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是一个测试类的糟糕命名,因为它太过于泛泛而谈。我建议将其命名为ArticleTest,以便用于Java单元测试。

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);

    }

}

1
如果您将其作为真正的单元测试而不是集成测试运行,则无需在具有Spring自动装配的容器中运行。在您的评论中,我认为您提到了尝试这样做,并且您指出还需要提供一系列无限的链接对象引用。但有一个解决办法。Mockito提供了一些预定义的Answer类,您可以使用它们初始化您的模拟对象。您可能需要查看RETURNS_DEEP_STUBS,这可能会让您摆脱这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接