JSF Bean sınıflarında FacesContext gibi nesneler kullandığınızda ve bu sınıflara mock nesneler ile birim testleri yazdığınızda Mockito, JMock gibi araçlar yeterli olmayacaktır. Örnek olarak aşağıdaki metodu inceleyelim.
public void login(ActionEvent ae) { FacesContext context = FacesContext.getCurrentInstance(); isSuccess = false; if (username.isEmpty() || password.isEmpty()) { context.addMessage(null, EMPTY_FIELD); return; } LoginResult result = loginManager.login(username, password); if(invalidUsername(result) || invalidPassword(result)) { context.addMessage(null, INVALID_LOGIN); } else if(isLoginSuccess(result)) { isSuccess = true; } }
Bu metod basit bir login işlemi gerçekleştirmektedir. Anlaşılacağı gibi xhtml sayfasında bir buton üzerinde actionListener parametresinde tanımlanmaktadır. Öncelikle kullanıcı adı ve parolanın boş bırakılıp bırakılmadığını kontrol etmekte eğer alanlar boş değilse business katmanı ile login işlemini gerçekleştirmektedir.
Şimdi kodları inceleyelim. 2. satır da JSF sayfasına mesaj göndermek için FacesContext nesnesini FacesContext.getCurrentInstance() statik metodu ile almaktadır. 3. satırda bir sınıf değişkeni yer almakta bu değişken sayesinde başka bir metod oturum açma işlemini gerçekleştirmektedir. 4. satırdaki koşul kullanıcı adı ve parola alanlarının boş bırakılıp bırakılmadığını kontrol etmektedir. 8. satır da ise loginManager nesnesi iş katmanının bir nesnesi olup DI (Dependency Injection) ile enjekte edilmektedir. LoginManager bir interface'dir. Bu sayede LoginBean sınıfının iş katmanına bağımlılığı esnek olur. Aynı zamanda test edebilmek için kolayca mock nesneleri oluşturabiliriz. Alt katmandan gelen sonuç ise bir LoginResult nesnesidir.
Burdaki temel sorun 2. satırdaki FacesContext nesnesinin statik bir metod aracılığı ile alınmış olmasıdır. Statik metodları Mockito gibi araçlarla taklid edemeyiz. Burda benim aklıma gelen ilk çözüm, bir Java anotasyonu oluşturup, setter injection ile uygulama bir servlet container içerisinde çalıştığında gerçek FacesContext nesnesini enjekte etmesini sağlamak ve test edildiğinde ise kolayca mock nesneyi enjekte edebilmekti. Diğer çözümler için internette arama yaparken PowerMock ile karşılaştım. PowerMock ile statik metodlara hatta private alanlara müdahale edebiliyorsunuz.
Örnekte PowerMock ile Mockito araçlarını beraber kullandım. Mockito yerine dilediğiniz test aracını kullanabilirsiniz. (JMock, EasyMock vs.)
Öncelikle bir setUp metodu oluşturalım:
public class LoginBeanTest { /** * FacesContext mock nesnesi */ private FacesContext context; /** * Kullanıcı adı */ private String username = "admin"; /** * Testler için kullanılan boş string */ private String empty = ""; /** * parola */ private String password = "password"; /** * Test edilen sınıf nesnesi */ private LoginBean loginBean; /** * Business nesnesi */ private LoginManager manager; /** * Testler için gerekli verileri hazırlar */ @Before public void setUp() throws Exception { context = Mockito.mock(FacesContext.class); PowerMockito.mockStatic(FacesContext.class); PowerMockito.doReturn(context).when(FacesContext.class, "getCurrentInstance"); loginBean = new LoginBean(); manager = Mockito.mock(LoginManager.class); loginBean.setLoginManager(manager); }
32. satır da FacesContext sınıfı Mockito ile taklid edilmektedir. 37. satırda benzer şekilde LoginManager sınıfı taklid edilmektedir. 36. satır da test edilecek olan sınıfın nesnesi üretilmektedir.
33. satır da mockStatic() metodu kullanılarak FacesContext nesnesinin statik metodları taklid edildi. 34-35. satırlarda PowerMockito.doReturn() metodu ile FacesContex.getCurrentInstance() statik metodunun davranışı değiştirilmiştir. Bu sayede birim testlerimizi çalıştırabiliriz. Örnek olarak bir test metodu ekleyelim:
[...] @Test public void loginEmptyUsername() throws Exception { loginBean.setUsername(empty); loginBean.setPassword(password); loginBean.login(null); Mockito.verify(context).addMessage(null, INVALID_LOGIN); } [...]
4-6. satırlarda gerekli test verileri girilip login işlemi gerçekleştirilmiştir. 7. satırda ise Mockito ile context.addMessage(null, INVALID_LOGIN) yani doğru metodun çağrılmasının testi yapılmıştır.
Bu şekilde testler çalıştırıldığında olumsuz sonuç alırız. PowerMockito'nun gereken sınıfları taklit etmesi için test sınıfının başına aşağıdaki gibi anotasyonlar tanımlanmalıdır:
@RunWith(PowerMockRunner.class) @PrepareForTest(FacesContext.class) public class LoginBeanTest { [...]
1. satırda testlerin PowerMockRunner ile çalışmasını ve 2. satırda ise FacesContext sınıfına ait statik metodun taklid edilmesini sağladık. Bu sayede testlerimiz çalışır hale gelmektedir.
PowerMock ile ilgili daha fazla bilgi almak için PowerMock'un web sayfasını inceleyebilirsiniz.
İyi çalışmalar…