Mockování s JMockit

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:

Napsat komentář