2012-10-12 42 views
7

मैक किए जा रहे सेवाओं से बचाता है मेरे पास एक डोलोल नियम फ़ाइल है जो नियमों में सेवा कक्षाओं का उपयोग करती है। तो एक नियम कुछ इस तरह है:लेनदेन संबंधी एनोटेशन

eval (countryService.getCountryById (1) = अशक्त!)

एक validationservice में @service और @Transactional (प्रचार = Propagation.SUPPORTS) drools फ़ाइल के साथ टिप्पणी की जाती है एक statelessNnowledgebase में प्रयोग किया जाता है और तथ्यों को जोड़ा जाता है जिसे डोलोल में इस्तेमाल किया जाना चाहिए। एक बार ऐसा करने के बाद session.execute (तथ्यों) का आह्वान किया जाता है और नियम इंजन शुरू होता है।

नियमों का परीक्षण करने के लिए मैं देश को सेवा देना चाहूंगा सेवा.getCountryById()। मॉकिटो का उपयोग करने में कोई बड़ी समस्या नहीं है। इसे अन्य सेवा के लिए किया गया है जो एक डोलॉल्स सेटअप का भी उपयोग करता है और यह ठीक काम करता है। हालांकि इस विशेष मामले में देश सेवा को ठोकर नहीं दिया गया था और मुझे पता नहीं लगा कि क्यों। बहुत समय बिताने और मेरे कोड की जांच करने के बाद मैंने पाया कि सेवा के ऊपर @ ट्रांसेक्शनल या इस एनोटेशन की कमी के कारण अंतर आया। @ ट्रांसेक्शन की कमी के कारण मॉकिटो ने किसी भी समस्या के बिना देश सेवा का मज़ाक उड़ाया, जिसमें @transactional जगह पर मॉकिटो असफल हो गया (बिना किसी त्रुटि या इशारा के) ने नकली इंजेक्शन दिया ताकि मूल देश सेवा वस्तु का उपयोग किया जा सके।

मेरा सवाल यह है कि यह एनोटेशन इस समस्या का कारण बनता है। @Transactional सेट होने पर mockito mocks इंजेक्ट क्यों नहीं कर सकता? साथ @

  • : मैंने देखा है कि mockito जब मैं डिबग और countryService का निरीक्षण जब यह drools सत्र मैं निम्नलिखित अंतर देखने के लिए वैश्विक रूप में जोड़ा जा रहा है जब मैं अपने debugwindow में countryservice निरीक्षण के रूप में विफल हो रहा है लेन-देन संबंधी: countryService @transactional बिना मूल्य countryService $$ EnhancerByCGLIB $$

  • b80dbb7b है: countryService मूल्य countryService $$ EnhancerByMockitoWithCGLIB $$ 27f34dc1

@t साथ इसके अलावा है देशव्यापी मेथोड में मेरा ब्रेकपॉइंट ransactional getCountryById पाया जाता है और डीबगर उस ब्रेकपॉइंट पर रुक जाता है, लेकिन @transactional के बिना मेरा ब्रेकपॉइंट छोड़ दिया जाता है क्योंकि मॉकिटो इसे छोड़ देता है।

ValidationService:

@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 methodes ठूंठ करने में सक्षम हो चाहता हूँ क्या करना है?

संबंध है,

माइकल

+0

आप और अधिक हो सकता है इस बात पर सटीक है कि आप कैसे जानते हैं कि मॉकिटो क्यों विफल रहा है? इसके अलावा, इस मुद्दे से संबंधित नहीं होने पर, आपको ध्यान रखना चाहिए कि मॉकिंग मान की वास्तव में अनुशंसा नहीं की जाती है, आपको इसके बजाय एक मूल्य उदाहरण स्वयं बनाना चाहिए, शायद आपके परीक्षण में एक कस्टम फैक्ट्री या निजी कन्स्ट्रक्टर आदि के साथ ... – Brice

+0

क्या आप भी 'बेसटेस्टडोमेन इंटीग्रेशन' और शायद वसंत कॉन्फ़िगरेशन के कुछ और दिखाएं यदि यह प्रासंगिक है। – Brice

+0

हाय ब्रिस, मैंने और जानकारी जोड़ दी है। बुलेट – Michael

उत्तर

4

हो रहा क्या है आपके ValidationService एक JdkDynamicAopProxy में लिपटे की जा रही है, इसलिए जब Mockito सेवा में mocks इंजेक्षन करने के लिए इसे उन्हें में इंजेक्षन के लिए किसी भी खेतों नहीं देखता चला जाता है। आप दो चीजों में से एक करने की आवश्यकता होगी:

  • त्याग अपने स्प्रिंग आवेदन संदर्भ और परीक्षण सिर्फ मान्यकरण सेवा शुरू करने, हर निर्भरता नकली के लिए मजबूर।
  • या JdkDynamicAopProxy से अपना कार्यान्वयन खोलें, और स्वयं को मोज़े लगाने से संभाल लें।

कोड उदाहरण:

@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

2

एक वैकल्पिक समाधान एक साथ पहले स्प्रिंग तारों सब कुछ वसंत संदर्भ के लिए नकली वस्तु को जोड़ने के लिए, इतना है कि यह होगा पहले से ही से पहले इंजेक्शन दिया गया है आपकी परीक्षण शुरू होते हैं। संशोधित परीक्षण कुछ इस तरह दिख सकता है:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = { Application.class, MockConfiguration.class }) 
public class TestForeignAddressPostalCode extends BaseTestDomainIntegration 
{ 

    public static class MockConfiguration { 

     @Bean 
     @Primary 
     public CountryService mockCountryService() { 
     return mock(CountryService.class); 
     } 

    } 

    @Autowired 
    protected CountryService mockCountryService; 

    @Autowired 
    private ValidationService level2ValidationService; 

    @BeforeMethod(alwaysRun=true) 
    protected void setup() 
    { 

    // set up you mock stubs here 
    // ... 

@Primary एनोटेशन महत्वपूर्ण है, अपने नए नकली CountryService इंजेक्शन के लिए सर्वोच्च प्राथमिकता है यह सुनिश्चित करें कि बनाने, सामान्य से एक की जगह। इसमें अनपेक्षित साइड इफेक्ट्स हो सकते हैं, हालांकि, यदि कक्षा को कई स्थानों पर इंजेक्शन दिया जाता है।

import org.mockito.Mockito; 
import org.springframework.aop.framework.Advised; 
import org.springframework.aop.support.AopUtils; 
import org.springframework.test.util.ReflectionTestUtils; 

@SuppressWarnings("unchecked") 
public class SpringBeanMockUtil { 
    /** 
    * If the given object is a proxy, set the return value as the object being proxied, otherwise return the given 
    * object. 
    */ 
    private static <T> T unwrapProxy(T bean) { 
    try { 
     if (AopUtils.isAopProxy(bean) && bean instanceof Advised) { 
     Advised advised = (Advised) bean; 
     bean = (T) advised.getTargetSource().getTarget(); 
     } 
     return bean; 
    } 
    catch (Exception e) { 
     throw new RuntimeException("Could not unwrap proxy!", e); 
    } 
    } 

    public static <T> T mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock) { 
    T mocked = Mockito.mock(classToMock); 
    ReflectionTestUtils.setField(unwrapProxy(beanToInjectMock), null, mocked, classToMock); 
    return mocked; 
    } 
} 

प्रयोग, सरल है कि आपका परीक्षण विधि की शुरुआत पर, साथ विधि mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock) फोन:

1

पर the answer of SuperSaiyen आधार पर, मैं यह सरल & प्रकार सुरक्षित बनाने के लिए एक ड्रॉप-में उपयोगिता श्रेणी का निर्माण बीन कि आप एक नकली इंजेक्ट करना चाहते हैं, और उस वस्तु की कक्षा जिसे मजाक किया जाना चाहिए। उदाहरण:

मान लीजिए कि आपके पास SomeService टाइप वाला बीन है जिसमें SomeOtherService की एक ऑटोवायर बीन है, जैसे कुछ;

@Component 
public class SomeService { 
    @Autowired 
    private SomeOtherService someOtherService; 

    // some other stuff 
} 

SomeService सेम पर someOtherService नकली करने के लिए, का उपयोग करें:

@RunWith(SpringJUnit4ClassRunner.class) 
public class TestClass { 

    @Autowired 
    private SomeService someService; 

    @Test 
    public void sampleTest() throws Exception { 
    SomeOtherService someOtherServiceMock = SpringBeanMockUtil.mockFieldOnBean(someService, SomeOtherService.class); 

    doNothing().when(someOtherServiceMock).someMethod(); 

    // some test method(s) 

    verify(someOtherServiceMock).someMethod(); 
    } 
} 

सब कुछ जितना वे काम करना चाहिए।

4

कृपया ध्यान दें कि वसंत 4.3.1, ReflectionTestUtils स्वचालित रूप से प्रॉक्सी को अनचाहे करना चाहिए। तो

ReflectionTestUtils.setField(validationService, "countryService", countryService); 

अब भले ही आपके countryService@Transactional, @Cacheable साथ टिप्पणी की जाती है काम करना चाहिए ... (कि, कार्यावधि में एक प्रॉक्सी के पीछे छिपा है)

संबंधित मुद्दा: SPR-14050