JMockit umožňuje mockování. Mockování je nahrazení objektů za mock objekty. Mock objekty jsou objekty, které představují (napodobují) nahrazovaný objekt a simulují jeho funkcionalitu tak, jak programátor potřebuje. Mockování se používá při testech a mockují se objekty, na kterých testovaná třída závisí, ale nejsou součástí testu, nebo je programátor nemůže ovlivnit. Může se jednat o dotazy do databáze, na webové služby, ….
Programátor například potřebuje otestovat, jak se jeho objekt chová, když zavolá službu a ta mu vrátí false, nebo nějaký složitý objekt. Tato služba zatím třeba ani neexistuje, nebo je velmi složité napsat na ni dotaz tak, aby vrátil požadovaný výsledek (dotaz musí projít validací a musí se přetransformovat do dalšího objektu tak, aby nevyhazoval výjimky atd.).
V následujícím příkladu použiji pro mockování knihovnu JMockit. Vytvořím si svoji třídu MyService, která bude nabízet metodu getAuthorizedPerson, která autorizuje id a v případě, že dané id bude autorizované, vrátí na jeho základě objekt Person. Moje třída bude volat dvě další služby, které jako programátor nemohu ovlivnit a nejsou ani součástí mého testu.
Třída Person.
public class Person {
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Externí služba pro autorizaci.
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class AuthorizationService {
private Connection connection;
public boolean isAuthorized(long id) throws SQLException {
PreparedStatement ps = connection.prepareStatement("select * from t_person where id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return true;
}
return false;
}
}
Třída pro dotazování do databáze.
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PersonDao {
private Connection connection;
public Person getPerson(long id) throws SQLException {
PreparedStatement ps = connection.prepareStatement("select id, name from t_person where id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Person person = new Person();
person.setId(rs.getLong("id"));
person.setName(rs.getString("name"));
return person;
}
return null;
}
}
Třída, kterou potřebuji otestovat
import java.sql.SQLException;
public class MyService {
public Person getAuthorizedPerson(long id) {
// Find out if the person is authorized.
AuthorizationService as = new AuthorizationService();
boolean isAuthorized = false;
try {
isAuthorized = as.isAuthorized(id);
} catch (SQLException e1) {
throw new RuntimeException("Exception while authorizating");
}
// Get the person.
if (isAuthorized) {
PersonDao pd = new PersonDao();
try {
Person person;
person = pd.getPerson(id);
return person;
} catch (SQLException e) {
throw new RuntimeException("Exception while getting person");
}
} else {
throw new RuntimeException("Person is not authorized");
}
}
}
Pro tento příklad je třeba přidat závislosti na JUnit a JMockit do projektu:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.20</version>
</dependency>
Můj test.
import static org.junit.Assert.*;
import java.sql.SQLException;
import mockit.Mock;
import mockit.MockUp;
import org.junit.Before;
import org.junit.Test;
public class MyServiceTest {
private MyService myService;
private AuthorizationService authorizationService;
private PersonDao personDao;
@Before
public void prepare() {
myService = new MyService();
authorizationService = new AuthorizationService();
personDao = new PersonDao();
}
@Test(expected = RuntimeException.class)
public void getAuthorizedPersonFailTest() {
// Mock AuthorizationService to return false.
mockAuthorizationService(false);
// Test MySevice#getAutohorizedPerson.
myService.getAuthorizedPerson(1L);
}
@Test
public void getAuthorizedPersonOKTest() {
// Mock AuthorizationService and its isAuthorized method to return true.
mockAuthorizationService(true);
// Mock PersonDao#getPersonMethod to return specific Person object.
mockGetPersonMethod();
// Test MySevice#getAutohorizedPerson.
Person p = myService.getAuthorizedPerson(1L);
assertNotNull(p);
assertTrue(p.getId() == 101L);
assertTrue(p.getName().equals("František Koudelka"));
}
private void mockAuthorizationService(boolean isAuthorized) {
new MockUp<AuthorizationService>() {
@Mock
public boolean isAuthorized(long id) throws SQLException {
return isAuthorized;
}
};
}
private void mockGetPersonMethod() {
new MockUp<PersonDao>() {
@Mock
public Person getPerson(long id) throws SQLException {
Person p = new Person();
p.setId(101L);
p.setName("František Koudelka");
return p;
}
};
}
}
Vysvětlení:
new MockUp<PersonDao>() {
@Mock
public Person getPerson(long id) throws SQLException {
Person p = new Person();
p.setId(101L);
p.setName("František Koudelka");
return p;
}
};
Vytvoří mock objekt PersonDao, jehož metoda getPerson vždy vrátí objekt Person s id 101 a name „František Koudelka“. Podobné je to pro AuthorizedService, jejíž metoda isAuthorized vrátí buď true nebo false dle toho, co zadáme jako parametr při volání mockAuthorizationService.
@Before
public void prepare() {
myService = new MyService();
authorizationService = new AuthorizationService();
personDao = new PersonDao();
}
Tento kód se provede před každým testem (díky anotaci @Before
). To znamená, že před každým testem se znovu inicializují tyto proměnné.
Zdroje: