Geçenlerde JSFUnit’i değişikliğe uğratarak WicketTester benzeri bir test altyapısı kurmaya çalıştım. WicketTester, istemci çağrılarını tek bir noktada ele alıyor. JSFUnit, HtmlUnit, Selenium gibi istemci- sunucu iletişimi söz konusu olmuyor.
Ancak JSFUnit in-container test framework. Bu nedenle uygulamanın testlerle birlikte sunucuya deploy edilmesini ve sunucunun çalıştırılmasını bekliyor. HtmlUnit vasıtası ile sunucu tarafında uygulamaya request’ler göndererek JSFSession ile sayfanın durumunu, FacesContext nesnesini ve dönen cevaptaki html içeriğini test etmeye çalışıyor. Projelerimizde genellikle UI düzeyinde fonksiyonel testleri gerçekleştirmek için uygulamanın ihtiyaç duyduğu Spring ApplicationContext’in de initialize olması gerekmektedir. Bu da aslında web container ile uygulamanın ayağa kaldırılmasına denk gelmektedir. Ayrıca WicketTester’a birebir benzeyen bir test altyapısını oluşturmak için dönen cevap içerisindeki html sayfasının işlenmesi, web requestlerinin oluşturulması, DOM ağacının kısmi güncellenmesi, javascript ile ilgili işler gibi konularda kapsamlı çalışma yapmak gerekmektedir. Bütün bu noktaları değerlendirdiğimizde mevcut çözümlerden JSFUnit, Jetty, JUnit gibi çözümleri bir araya getirerek HTTP tabanlı istemci-sunucu iletişimine dayanan in-container tabanlı fakat standalone çalıştırılan bir tester oluşturmak daha anlamlı gözüktü.
Geliştirilen JSFTester çözümünün içerisinde JSFUnit’in kullanımı söz konusudur. JSFTester’ da JSFUnit’in JSFSession nesnesi üzerinden JSFClientSession ve JSFServerSession nesnelerine erişmek gerekiyor. JSFUnit’deki ClientSession HtmlUnit ile dönen cevabı incelemeye, ServerSession ile de en son request sonucu oluşan FacesContext nesnesine, UIComponent ağacına ve sunucu tarafındaki “faces managed bean”lara erişmeye yardımcı oluyor. JSFServerSession nesnesi runtime’da FacesContext’in intialize olmasını bekliyor.
JSFTester içerisinde Jetty web sunucusunu gömülü olarak çalıştırıp HttpSession nesnesini, JSFTester’a ServletRequestListener yardımı ile iletmeyi denedim. İletilen Session JSFUnit tarafında bir ThreadLocal değişken vasıtası ile en son request’e ait FacesContext nesnesini almak için kullanılıyor.
Bu noktaya kadar başarılı oldum ancak testcase, servlet container’ın classloader’ından farklı bir classloader ile çalıştırıldığından, JSFServerSession’ın çalışması için gerekli olan faces initialization o classloader’da yapılmamış oluyordu. Bu durumda dönen cevap üzerinde herhangi bir html elemanına click’lediğimiz vakit JSFUnit tarafında faces-initialization’a tabi tutulmuş bir thread context olmadığından problem oluyordu. Bunu aşmak içinde Jetty’nin WebAppContext’inin classLoader’ını testcase’i çalıştıran thread’in contextClassLoader’ı ile değiştirmek gerekiyor. Daha öncesinde de aynı class’ın Jetty’nin WebAppClassLoader’ı tarafından tekrar yüklenmesi nedeniyle ortaya çıkan bir class cast probleminden ötürü WebAppContext’in parentLoaderPriority değeri true yapılmıştı. Bu sayede WebAppClassLoader öncelikle kendisi değil, parent class loader’ı ile sınıfları yüklemeye çalıyordu.
Şu ana kadar karşıma çıkan problemleri bir biçimde çözüme kavuşturdum. Ancak muhtemel en büyük problem noktası olarak JSFTester’in ve Jetty’nin ayrı ayrı thread’lerde çalışması şimdilik uyuyan bir yanardağ gibi duruyor. Ayrıca HttpSession timeout olup, yeni bir session yaratıldığı vakit bu session’ın da Thread Local değişkene tekrar set edilmesi gerekiyor.