Showing posts with label Spring. Show all posts
Showing posts with label Spring. Show all posts

13 February 2013

Breaking the Spring by example

First of all, I'm not against Spring, IoC frameworks or frameworks in general. Quite the contrary, I'm big fan of many different frameworks. Said that I must admit that sometimes when a framework hides too much magic you risk loosing control of your application. Once you realize that you've just created zombie app you are afraid of, it may be too late ;) So be careful with framework selection even if you think of commonly used framework like Spring...

Spring - ultimate IoC container


Spring Framework is one of the most popular IoC frameworks and probably the richest in terms of provided features. With every new release number of capabilities, supported technologies increases making the Spring attractive candidate for wide range of Java projects.

One would expect that using such matured, popular, battle-proofed framework should be easy and straightforward at least when it comes to its core functionality. You just annotate your beans, create configuration bean and everything should work as expected. But sometimes it doesn't... As it turns out, having large number of capabilities doesn't mean you can freely play with them, mix them and expect that Spring handles all cases seamlessly.

While working recently with Spring I've found several cases when mixing some features resulted in unexpected or undocumented behavior. I was also surprised to find out
that some features were missing and to solve the problem I had to find a workaround.

Don't have to say, the often you are being surprised by the framework, the often you are wondering if the decision of using it was correct... After all, the time you lost on learning nuances, limitations and bugs of the framework you could better spend on implementing your own infrastructure, especially if all that you need is just a small subset of what the framework provides.

Let's go to the examples.
I will show you how to make Spring surprise you (well, at least it surprised me).

Let's break the Spring

1) Lazy secondary beans (using @Primary and @Lazy annotations)

Spring provides @Primary annotation that should be used to indicate "that a bean should be given preference when multiple candidates are qualified to autowire". Great, that could be useful for tests. By configuring primary beans inside test configuration we could easily replace default beans from main configuration.

Main configuration:

Additional test configuration:


When starting test, the test service will be instantiated. Since default service bean is configured as lazily instantiated, it should not be instantiated by Spring because it is not used at all, right? Well, unfortunately Spring ignores @Lazy annotation in this case. So you can be surprised if you put some heavy initialization logic inside your default service and expect that it will not be executed during the tests...

2) @Primary annotation and service locator

Sometimes when working with prototype scoped beans it is necessary to ask Spring for specific bean using service locator pattern. Please see example below that complements the code from example 1:

Surprisingly Spring will throw exception when trying to run the code above complaining that two candidates of type MyService are available... This is just a bug, nothing more.

3) Events and prototype listeners

Spring provides support for event base communication. You can easily publish application event from one corner of your system and handle the event on the other corner by registering appropriate event listener. It works until you need your listener to be prototype scoped bean.
The Spring will notify only one instance of listener about the event, the instance that has been created during Spring initialization (when ContextRefreshedEvent is published by Spring). There is no way to inform all instantiated prototypes about the event.

4) Custom qualifiers and annotation-based configuration

Using annotations like @Qualifier you can control the selection of candidates among multiple matches. But default usage of @Qualifier allows only bean names (Strings) as identifiers that is error-prone and refactoring-unfriendly.

Fortunately there is another solution. You can define your own annotation that can be used as classifier. Just use @Qualifier as meta-annotation! Example of such custom qualifier you can find below:

You can than define several beans of the same type but with different qualifier type.

..and inject independently beans with different qualifiers:

So far so good. Now, where is the surprise you may ask... As you probably noticed, we used xml configuration to define beans with different qualifiers. Nobody uses xml nowadays ;) so there must be a good reason to use it... The reason is that (surprise!) you can't define several beans of the same type with different custom qualifier using annotation-based configuration because it is not possible to annotate factory method with custom qualifier:

! The code below does not compile !


You can only annotate the type with custom qualifier:

but this approach requires new class for each qualifier type.

I don't know why Spring does not support annotating factory method inside configuration as described above. You will find only the following explanation In Spring documentation:

"As with most annotation-based alternatives, keep in mind that the annotation metadata is bound to the class definition itself, while the use of XML allows for multiple beans of the same type to provide variations in their qualifier metadata, because that metadata is provided per-instance rather than per-class."

5) Properties and annotation parameters

What's strange about the following code?

Configuration for scheduler 1 is read from  Properties while for scheduler 2 it is hardcoded. Why? Because you can assign property expression (which is String) only to parameters of type String. Unfortunately fixedRate parameter is of type long... The solution would be to switch to xml configuration (again? oh no..) or implement some workaround: http://stackoverflow.com/questions/11608531/injecting-externalized-value-into-spring-annotation.

6) ...

I guess if you worked with Spring you could put another case here.
Feel free to drop a comment on your biggest surprise from Spring.


21 December 2010

Mockowanie przy użyciu Spring i Mockito

W poniższym wpisie chciałbym przedstawić jak efektywnie skonfigurować testy integracyjne w Spring z użyciem mocków.

Testy integracyjne w Spring


Tworzenie i uruchamianie testów integracyjnych w Spring jest dziecinnie proste dzięki dobrodziejstwom jakie dostarcza Spring TestContext Framework.
Zakładając, że plik xml konfiguracji kontekstu aplikacji Spring (Spring Application Context) w module aplikacji, który chcemy testować, nazwaliśmy applicationContext.xml, uruchomienie testu integracyjnego JUnit dla takiego moduły wygląda następująco:



Jest to najprostszy sposób na przetestowanie poprawności pliku konfiguracji kontekstu aplikacji, w którym zdefiniowane są zależności pomiędzy klasami zarządzanymi przez Spring.

No dobrze, ale chcemy przetestować konkretny serwis, który stworzyliśmy. Zatem do naszego testu wstrzykujemy serwis i tworzymy dla niego metodę testującą:



Tworzenie mocka


Nasz serwis korzysta z kilku innych serwisów, w tym z serwisu HttpClient, który w naszym teście musimy zastąpić mockiem.
Jednym ze sposobów jest ręczna zmiana zależności w kodzie testu:



Sposób ten ma jednak kilka wad:

- Dla każdego testu (metody testującej) musimy sami tworzyć i przypisywać mocka

- Serwis musi dostarczać settera dla zależności mockowanej, podczas gdy w przypadku wstrzykiwania zależności za pomocą adnotacji setter nie jest wymagany

- Mockujemy tylko konkretną zależność między dwoma beanami. Jeżeli serwis który mockujemy jest używany przez inny serwis biorący udział w teście (np. DoSthService używa HttpClient i OtherService, a OtherService używa HttpClient) , musimy mockować każdą zależność osobno

Lepszym rozwiązaniem jest podmiana serwisu HttpClient bezpośrednio w kontekście aplikacji. Oczywiście nie możemy zmienić pliku applicationContext.xml, musimy stworzyć odrębny kontekst aplikacji i wskazać go w konfiguracji naszego testu:



No dobrze, ale jak stworzyć mocka w pliku konfiguracji kontekstu aplikacji, skoro nasz mock nie jest utworzoną przez nas osobną klasą, ale został wygenerowany automatycznie przez framework Mockito ( Mockito.mock(HttpClient.class) ) ?

Rozwiązanie jest proste. Metodę Mockito.mock należy zadeklarować jako metodę fabrykującą naszego mocka podając jako argument tej metody klasę obiektu mockowanego:

Dostrajanie konfiguracji


Kopiowanie całej konfiguracji kontekstu aplikacji w celu nadpisania jednego serwisu prowadzi do redundancji kodu (kodu konfiguracji), a zatem zwiększa koszt utrzymania aplikacji.
Jeżeli chcemy tego uniknąć, możemy zdefiniować dwa pliki konfiguracji kontekstu aplikacji w konfiguracji naszego testu:



W pliku applicationConfig-test.xml definiujemy tylko mocki, które zastąpią istniejące beany w konfiguracji głównej.
Zwróćmy uwagę na kolejność deklaracji plików konfiguracji. Ważne jest aby deklaracja pliku konfiguracji mocków następowała po deklaracji pliku głównego konfiguracji.

Autowstrzykiwanie mocków


W przypadku gdy korzystamy z autowstrzykiwania zależności, nasz mock będzie dostępny tylko gdy wstrzykiwanie zależności odbywa się na podstawie nazwy. A zatem mock nie zostanie znaleziony jeśli używamy adnotacji @Autowired, dla której wstrzykiwanie odbywa się na podstawie typu. Rozwiązaniem może być dodanie kwalifikatora wskazującego nazwę zależności:


Jednak lepszym rozwiązaniem jest zastosowanie adnotacji @javax.annotation.Resource. W tym przypadku zależność najpierw wyszukiwana jest po nazwie, a w przypadku braku pasującej nazwy, po typie.

Włączanie/wyłączanie mocków


Czasami ten sam test integracyjny chcemy uruchamiać zarówno z serwisem w postaci mocka jak i z serwisem rzeczywistym. Jeśli stosujemy opisaną powyżej metodę nadpisywania beanów z konfiguracji głównej mockami z konfiguracji testowej, włączenie/wyłączenie mocka można łatwo osiągnąć modyfikując plik konfiguracji mocków (dodając bądź usuwając mocka). Jednak co z kodem sterującym zachowaniem mocka (patrz kod poniżej)?



Uruchomienie tego kodu na obiekcie nie będącym mockiem zakończy się wyjątkiem. Należy zatem kod sterujący mockiem wykonać warunkowo tylko jeśli obiekt rzeczywiście jest mockiem. Sprawdzenie implementujemy następująco:


Współdzielenie mocka


Jeżeli chcielibyśmy użyć tego samego mocka w kilku metodach testowych musimy pamiętać o zresetowania stanu mocka przed każdym testem.
Najprostszym rozwiązaniem jest dodanie do klasy testu metody resetMocks z adnotacją @org.junit.Before:


Mockowanie częściowe


Na koniec przedstawię w jaki sposób utworzyć w konfiguracji kontekstu aplikacji częściowego mocka, czyli obiekt, którego zachowanie tylko w części chcemy mockować (np. zmieniając tylko rezultat wywołania metody):

W tym przypadku tworzymy mocka przy pomocy metody fabrykującej Mockito.spy, podając jako argument tej metody referencję do istniejącego bean-a.