Before Advice is never called when running unit tests

Hi guys,

I am currently trying to implement a AOP Before Advice (https://wiki.openmrs.org/display/docs/OpenMRS+AOP). The final goal is to ensure that a visit cannot be closed if there is no diagnosis entered. But for now, this advice is just a dumb class that throws a APIException any time the VisitService.endVisit(visit) method is called.

public class BeforeEndVisitAdvice implements MethodBeforeAdvice{

	protected static final Log log = LogFactory.getLog(BeforeEndVisitAdvice.class);

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {

		if ( method.getName().equals("endVisit") && (target instanceof VisitService) ) {
			log.error("Dumb 'before advice' that just returns an APIException");
			throw new APIException();
		}
		return;		
	}
}

This works just fine at runtime: the APIException is thrown.

Now the test looks like this for now:

public class BeforeEndVisitAdviceTest  extends BaseModuleContextSensitiveTest {

	protected final Log log = LogFactory.getLog(BeforeEndVisitAdvice.class);

	@Autowired
	private VisitService visitService;
	@Autowired
	private PatientService patientService;
	@Autowired
	private LocationService locationService;	
	@Autowired
	private AdtService adtService;
	
	@Before
	public void before() {
	}

	@Test(expected=APIException.class)
	@Verifies(value = "should fail ending a visit that has no diagnosis", method = "before(Method, Object[], Object)")
	public void shouldFailEndVisitWithNoDiagnosis() {

		VisitDomainWrapper activeVisitWrapper = adtService.getActiveVisit(patientService.getPatient(2), locationService.getLocation(1));  
		
		List<Diagnosis> diags = activeVisitWrapper.getUniqueDiagnoses(true, true);
		visitService.endVisit(activeVisitWrapper.getVisit(), new Date());		
	}
}

But when running the tests (from Eclipse or Maven) no APIException is returned. The BeforeEndVisitAdvice.before(...) method is never called.

I have had a look at AuthorizationAdviceTest.java and did not see anything particular there that I may have forgotten. Looks like it should just work out of the box.

Do I need to initialize/start/configure the advice somewhere when running tests? What did I miss?

Can i look at how you have configured it in your module’s config.xml?

@mksrom, I could be wrong here, but I believe advice is added in config.xml for modules, which is not something that is loaded as a part of Spring start-up. Core advice would be wired in differently and so I think you can expect to see any core-based AOP, but possibly not any module-added AOP, without explicitly wiring it? @dkayiwa?

@mseaton you are absolutely correct. That is why i asked for his config.xml such that i give him a sample of how to explicitly wire it in a module as below:

Class<?> cls = Context.loadClass(“org.openmrs.api.FormService”); Class<?> adviceClass = Context.loadClass(“org.openmrs.module.xforms.aop.FormDeleteAfterAdvice”); AdvicePoint advice = new AdvicePoint(“org.openmrs.api.FormService”, adviceClass); Object aopObject = advice.getClassInstance(); Context.addAdvice(cls, (Advice) aopObject);

@dkayiwa, here is my config.xml:

<!-- AOP -->
<advice>
	<point>org.openmrs.api.VisitService</point>
	<class>${project.parent.groupId}.${project.parent.artifactId}.advice.BeforeEndVisitAdvice</class>
</advice>

OK, let me try to add my advice to the Context with the snippet you suggest.

Thanks @mseaton for the explanation, I understand why I need to explicitly wire it now. Though I tried something like that but didn’t quite know how to call the Context.addAdvice(cls,advice) (a NPE was returned)

@dkayiwa, that works. Thanks a lot :thumbsup:

Here is the code I’ve added:

@Before
public void before() throws ClassNotFoundException {

    Class<?> cls = Context.loadClass("org.openmrs.api.VisitService");
    Class<?> adviceClass;
    adviceClass = Context.loadClass("org.openmrs.module.lfhcforms.advice.BeforeEndVisitAdvice");
    AdvicePoint advice = new AdvicePoint("org.openmrs.api.VisitService", adviceClass);
    Object aopObject = advice.getClassInstance();
    Context.addAdvice(cls, (Advice) aopObject);

}