11

ルールでサービス クラスを使用する drools ルール ファイルがあります。したがって、1 つのルールは次のようになります。

eval(countryService.getCountryById(1) != null)

@service および @Transactional(propagation=Propagation.SUPPORTS) でアノテーションが付けられた検証サービスでは、drools ファイルが statelessKnowledgebase で使用され、drool で使用する必要があるファクトが追加されます。それが完了すると、session.execute(facts) が呼び出され、ルール エンジンが開始します。

ルールをテストするために、countryService.getCountryById() をスタブしたいと思います。mockitoを使用しても大きな問題はありません。drools セットアップを使用する他のサービスに対してもこれを実行すると、正常に機能しました。ただし、この特定のケースでは、countryService がスタブ化されておらず、その理由がわかりませんでした。多くの時間を費やしてコードをチェックした後、 @Transactional をサービスの上に置くか、このアノテーションを欠くことが違いを生むことがわかりました。@Transaction がないため、mockito は問題なく countryservice のモックを作成しました。 @transactional を配置すると、mockito は (エラーやヒントなしで) モックの注入に失敗し、元の countryservice オブジェクトが使用されました。

私の質問は、なぜこの注釈がこの問題を引き起こすのかということです。@Transactional が設定されている場合、mockito がモックを注入できないのはなぜですか? drools セッションにグローバルとして追加されるときに countryService をデバッグおよび検査すると、mockito が失敗することに気付きました。デバッグ ウィンドウで countryService を検査すると、次の違いが見られます。

  • @transactional を使用: countryService の値は CountryService$$EnhancerByCGLIB$$b80dbb7b です。

  • @transactional:countryService がない場合、値は CountryService$$EnhancerByMockitoWithCGLIB$$27f34dc1 になります。

さらに @transactional を使用すると、countryservice メソッドの getCountryById のブレークポイントが見つかり、デバッガーはそのブレークポイントで停止しますが、@transactional がないと、mockito がバイパスするため、ブレークポイントはスキップされます。

検証サービス:

@Service
@Transactional(propagation=Propagation.SUPPORTS)
public class ValidationService 
{
  @Autowired
  private CountryService countryService;

  public void validateFields(Collection<Object> facts)
  {
    KnowledgeBase knowledgeBase = (KnowledgeBase)AppContext.getApplicationContext().getBean(knowledgeBaseName); 
    StatelessKnowledgeSession session = knowledgeBase.newStatelessKnowledgeSession();
    session.setGlobal("countryService", countryService);
    session.execute(facts);

  }

そしてテストクラス:

public class TestForeignAddressPostalCode extends BaseTestDomainIntegration
{

  private final Collection<Object> postalCodeMinLength0 = new ArrayList<Object>();

  @Mock
  protected CountryService countryService;

  @InjectMocks
  private ValidationService level2ValidationService;


  @BeforeMethod(alwaysRun=true)
  protected void setup()
  {
    // Get the object under test (here the determination engine)
    level2ValidationService = (ValidationService) getAppContext().getBean("validationService");
    // and replace the services as documented above.
    MockitoAnnotations.initMocks(this);

    ForeignAddress foreignAddress = new ForeignAddress();
    foreignAddress.setCountryCode("7029");
    foreignAddress.setForeignPostalCode("foreign");

    // mock country to be able to return a fixed id
    Country country = mock(Country.class);
    foreignAddress.setLand(country);
    doReturn(Integer.valueOf(1)).when(country).getId();

    doReturn(country).when(countryService).getCountryById(anyInt());

    ContextualAddressBean context = new ContextualAddressBean(foreignAddress, "", AddressContext.CORRESPONDENCE_ADDRESS);
    postalCodeMinLength0.add(context);
  }

  @Test
  public void PostalCodeMinLength0_ExpectError()
  {
    // Execute
    level2ValidationService.validateFields(postalCodeMinLength0, null);

  }

この @transactional アノテーションを保持したいが、countryservice メソッドをスタブ化できるようにしたい場合はどうすればよいですか?

よろしく、

マイケル

4

5 に答える 5

9

Whats happening is your ValidationService is being wrapped in a JdkDynamicAopProxy, so when Mockito goes to inject the mocks into the service it does not see any fields to inject them into. You'll need to do one of two things:

  • Forego starting your Spring Application Context and test just the Validation Service, forcing you to mock every dependency.
  • Or unwrap your implementation from the JdkDynamicAopProxy, and handle injecting the mocks yourself.

Code Example:

@Before
public void setup() throws Exception {
    MockitoAnnotations.initMocks(this);
    ValidationService validationService = (ValidationService) unwrapProxy(level2ValidationService);
    ReflectionTestUtils.setField(validationService, "countryService", countryService);
}

public static final Object unwrapProxy(Object bean) throws Exception {
    /*
     * If the given object is a proxy, set the return value as the object
     * being proxied, otherwise return the given object.
     */
    if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
        Advised advised = (Advised) bean;
        bean = advised.getTargetSource().getTarget();
    }
    return bean;
}

Blog entry on the issue

于 2013-07-09T12:53:31.250 に答える