Spring Cache Unit Test

Our typical business service with a cacheable method, as shown below:

@Service
public class AccountService{
	@Autowired
	public AccountDao accountDao;
	
	@Cacheable(value = "accounts")
	public Account getAccount(int accountId) {
		return accountDao.getObjectById(accountId);
	}
}
Primary concerns for this kind of service method unit test.
Concern 1: combine SpringJUnit4ClassRunner and Mockito
Solution: For common scenarios, we can apply the standard solution:

@Mock
	private AccountDao dao;

	@InjectMocks
	@Autowired
	private AccountService accountService;
	
	@Before
	public void setup() throws Exception {
		MockitoAnnotations.initMocks(this);
	}
Concern 2: Mockito and Spring proxies, the critical part is our method annotated with @Cacheable, then totally a different story now.
Solution: As we applied @Cacheable annotation(another annotations like @Transactional has same effect), Spring would generate a proxy automatically, thus direct @InjectMocks @Autowired does not work. Reference 1 taught me the following approach and it works.

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:spring.xml" })
@ActiveProfiles("test")
public class AccountCacheTest {

	@Mock
	private AccountDao dao;

	@InjectMocks
	@Autowired
	private AccountService accountService;

	@Before
	public void setup() throws Exception {
		MockitoAnnotations.initMocks(this);
		AccountService bas = (AccountService) unwrapProxy(accountService);
		ReflectionTestUtils.setField(bas, "accountDao", dao);
	}

	@Test
	public void testAccoutCache() throws Exception {
		/* Arrange. */
		Account acc1 = new Account();
		acc1.setId(1);

		when(dao.getObjectById(1)).thenReturn(acc1, acc1);

		/* Act. */
		accountService.getAccount(1);
		accountService.getAccount(1);

		/* Assert. */
		verify(dao, times(1)).getObjectById(1);
	}

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

LESSON: When test cache, we'd better utilize the assertSame.


Reference

1. http://kim.saabye-pedersen.org/2012/12/mockito-and-spring-proxies.html

2. http://www.captaindebug.com/2012/09/spring-31-caching-and-cacheable.html#.UmDsiflHK8s

    http://www.captaindebug.com/2012/09/spring-31-caching-and-cacheevict.html#.UmDhNflHLQI




你可能感兴趣的:(Spring Cache Unit Test)