07.02.2011

Test metody tvořící objekty



Kdo píše unit testy (a to je snad už většina), se s touhle situací určitě setkal. Máte metodu vytvářející ve svém těle nějaký objekt a posíljící ho na nějaký interface nebo do další vrstvy aplikace. Typicky servisní metoda vytvoří doménový objekt a pošle ho do DAO vrstvy. Teď jak ale napsat elegantně unit test, když DAO vrstvu mockujete?

My jako knihovnu pro mock objekty používáme EasyMock a i tam byla taková situace nepříjemná, protože konfigurování mocku vyžaduje přesný objekt, se kterým se metoda bude volat. Tady ale máme objekt nově vytvořený testovanou metodou. Co s tím? Dříve se dal implementovat tzv. IArgumentMatcher, ale to bylo celkem nepohodlné a bylo to hodně kódu. Od verze EasyMocku 2.4 máte ale možnost využít metodu capture().

Máme tedy následující kód servisní vrstvy a chceme ho testovat v izolaci. Rozhraní UserDao budeme chtít mockovat.

public class UserServiceImpl implements UserService {
	private UserDao userDao;
	
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	@Override
	public void createUser(String username) {
		User user = new User(username);
		userDao.save(user);
	}

}

Všimněte si, jak metoda createUser() dostává jako argument String, ale do DAO objektu už posílá vytvořený doménový objekt User. Když bydeme psát unit test, tak budeme určitě chtít zkontrolovat, jestli posílaný uživatel byl metodou vytvořen a nastaven, jak očekáváme. K tomu nám přesně pomůže metoda capture().

public class UserServiceTest {
	private UserServiceImpl userService;
	private UserDao userDao;
	
	@Before
	public void setUp() {
		userService = new UserServiceImpl();
		userDao = createMock(UserDao.class);
		userService.setUserDao(userDao);
	}

	@Test
	public void createUser() {
		Capture<User> capturedUser = new Capture<User>();
		userDao.save(capture(capturedUser));
		replay(userDao);
		
		userService.createUser("pavel");
		
		User user = capturedUser.getValue();
		assertEquals("pavel", user.getUsername());
		
		verify(userDao);
	}

}

Důležité jsou především tyto řádky konfigurijící EasyMock k zachycování zaslaných argumentů.

		Capture<User> capturedUser = new Capture<User>();
		userDao.save(capture(capturedUser));

Pak už jen stačí vzít zachycený doménový objekt a řádně zkontrolovat, jestli metoda funguje v pořádku.

		User user = capturedUser.getValue();
		assertEquals("pavel", user.getUsername());

Myslím, že psaní testů tímto způsobem, je elegantní a efektivní. Jenom mě trochu mrzí, že jsem si nové metody capture() nevšiml dřív, protože verze 2.4 je už nějaký ten pátek venku.

  • Luboš Račanský

    Jen dodám, že mně metoda capture() pomohla, když byl parametrem metody rozhraní nějaký objekt třetích stran, který neimplementoval equals.

  • Ono stačí i když je to tvůj objekt. Ale třeba equals() má jenom na primární klíč, ale ty chceš zkontrolovat, jestli testovaná metoda dobře naplnila i ostatní atributy.