Disabling certain aspects during unit test runs

2020-08-09 10:05发布

问题:

I have integration tests (load context) and unit tests running together. My code does aspectj compile time weaving using spring.

My problem is that my declared advises also run during some of my unit tests. This kills the notion of a unit test, which is why I would like to disable them.

Is there something I can put on the pointcut declaration, some method I can call, some spring configuration, or maven command that disables these advises for something like all *UnitTest.java?

Thanks for the help.


example:

I have the following unit test:

@RunWith(MockitoJUnitRunner.class)
public class CompanyServiceImplTest {
    @Test
    public void createCampaignTest() throws Exception {
        when(companyDaoMock.saveCompany(any(Campaign.class))).thenReturn(77L);

        Long campaignId = companyService.createCampaign(campaignMock);

        assertEquals(Long.valueOf(77L), Long.valueOf(campaignId));
    }
}

and following service method:

@Override
@Transactional
@EventJournal(type = EventType.CAMPAIGN_CREATE, owner = EventOwner.TERMINAL_USER)
public Long createCampaign(Campaign campaign) {
    return companyDao.saveCompany(campaign);
}

aspect:

@Aspect
public class EventJournalAspect {

    @Autowired
    private EventJournalService eventJournalService;

    @Pointcut(value="execution(public * *(..))")
    public void anyPublicMethod() {}

    @Pointcut("within(com.terminal.service..*)")
    private void inService() {}

    @AfterReturning(pointcut = "anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", returning = "id")
    public void process(Object id, EventJournal eventJournal, AbstractDomainEntity entity)
            throws Throwable {
        if (eventJournal.type() != EventType.CAMPAIGN_PAYMENT || id != null) {
            saveEvent(eventJournal, EventStatus.SUCCESS, entity, (Long) id);
        }
    }

    @AfterThrowing(pointcut = "anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", throwing="ex")
    public void processException(EventJournal eventJournal, AbstractDomainEntity entity, Exception ex) throws Throwable {
        saveEvent(eventJournal, EventStatus.FAILURE, entity, null);
    }

    private void saveEvent(EventJournal eventJournal, EventStatus status, AbstractDomainEntity entity, Long persistentId)   {
        EventType type = eventJournal.type();
        EventOwner owner = eventJournal.owner();
        eventJournalService.saveEvent(type, owner, EventStatus.SUCCESS, entity, persistentId);
    }

}

When test executes - eventJournalService is null. Thus I see NullPointerException

回答1:

The answer is simple: You want to use an if() pointcut expression.


Update (after the question has also been updated): The originally provided link above should contain enough information, but for what it is worth, a short explanation and a simple example:

An if() pointcut is a static aspect method returning a boolean. If the return value is true, it means that any combined pointcut like myPointcut() && if() matches as long as myPointcut() matches. For a return value of false the whole combined pointcut does not match, effectively deactivating any advice connected to the pointcut.

So what can you do in a static if() pointcut?

  • evaluate a static boolean member of some tool class like TestMode.ACTIVE which is only true during unit or integration testing
  • evaluate an environment variable which is only set during testing
  • evaluate a Java system property which is only set during testing
  • and many more things

If you want to do something fancier (and trickier) and performance is not so important, you can also try to dynamically determine whether the auto-wired aspect member variable equals null or not and only activate your pointcuts if the injected object is actually present. The only problem here is how to determine a member variable from a static method. I have no idea about Spring AOP, but in plain AspectJ there is the helper class Aspects with several overloaded methods named aspectOf(..). Assuming that your aspect is instantiated as a singleton, you could do something like this:

@Pointcut("if()")
public static boolean isActive() {
    return Aspects.aspectOf(PerformanceMonitorAspect.class).eventJournalService != null;
}

// ...

@AfterReturning(pointcut = "isActive() && anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", returning = "id")
// ...

@AfterThrowing(pointcut = "isActive() && anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", throwing="ex")
// ...


回答2:

I can only guess: The first thing is to have a separate Spring applicationContext-test.xml, without component-scan; In maven you can add a phase runtime excluding weaving jars for test.



回答3:

Compile time weaving would inline the advice calls in the targeted methods identified by the pointcuts that you have. I personally feel that it is good to unit test with the compile time weaving in place, because at runtime your unit does include the class with the advice inlined?

The thought I have to not include advice would be to have two different compile targets, one with compile time weaving, and one without, you should be able to do this through maven profiles, a dev profile not weaving advice in and a prod profile to weave the aspects in.



回答4:

You can write a method that returns if current execution was launched using JUnit framework.

The method can check stack trace with Thread.currentThread().getStackTrace() and search for MockitoJUnitRunner presence.

I tested this solution using a SpringJUnit4ClassRunner but I think could work with MockitoJUnitRunner.

Also, you can have got a static boolean field like:

private static boolean TEST_ENVIRONMENT = false;

In a class present in your project (not in your tests) and check the value in the control method instead of use stack trace.

When you run your tests, you can use @BeforeClass annotation to set TEST_ENVIRONMENT = true.

This solution only gives you a way to know if your code is running from a test or not.