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: