Zamockowany obiekt zwraca nullpointera

Zamockowany obiekt zwraca nullpointera
AP
  • Rejestracja:ponad 6 lat
  • Ostatnio:3 miesiące
  • Postów:164
0

Każdy moduł w mojej aplikacji ma publiczną fasade, a reszta jest package scoped, do tego całe DI jest realizowane wewnątrz takich klas:

Kopiuj
@Configuration
class EmailConfiguration {
    @Bean
    EmailFacade emailFacade(EmailSender emailSender) {
        return new EmailFacade(emailSender);
    }
}
Kopiuj

@Configuration
class UserConfiguration {
    @Bean
    UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
        return new UserFacade(repository, emailFacade);
    }
}

Dopóki mój moduł Usera nie miał zależności do modułu Email, mogłem dzięki temu pisać testy bez odpalania Springa. Package dla domeny i testów Usera jest taki sam, więc mam dostęp do package scope obiektów, co pozwalało na coś takiego

Kopiuj
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository());

Problem pojawił się z dojściem dodatkowej zależności, czyli wspomniany moduł Email.

Kopiuj
@RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
	@Mock
	private EmailFacade emailFacade;

	@InjectMocks
	private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);

	@Before
	public void setUp() {
		MockitoAnnotations.initMocks(this);
	}

	@Test
	public void shouldRegisterUser() {
		Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.right(new SuccessMessage("")));
		assertTrue(userFacade.registerNewUser(RegisterUserDto.builder()
				.username(VALID_USERNAME)
				.email(VALID_EMAIL)
				.password(VALID_PASSWORD).build()).isRight());
	}
}

Niestety takie coś rzuca nullpointera przy pierwszym odwołaniu się do emailFacade wewnątrz metody registerNewUser(), no i faktycznie - ta fasada jest nullem. Nie bardzo jednak mogę znaleźć sposobu na to by powyższy test zadziałał tak jak oczekuje.

Próbowałem również zrobić fake'ową implementację EmailSender, czyli zależności EmailFacade.

Kopiuj
@Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());

Niestety tu pojawia się problem - nie dostaję co prawda nullpointera, ale mocki nie działają na takim obiekcie.
Pomoże ktoś z tego wybrnąć?

edytowany 1x, ostatnio: AngryProgrammer
hcubyc
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 3 lata
1

A mockito nie ma jakiś metod do utworzenia mocka jawnie, a nie przez adnotacje?


Limitations are limitless > ##### Ola Nordmann napisał(a)
> Moim językiem ojczystym jest C++ i proszę uszanować to, że piszę po polsku.
AP
  • Rejestracja:ponad 6 lat
  • Ostatnio:3 miesiące
  • Postów:164
0

Jestem pewien że wczoraj próbowałem coś podobnego i nie działało ...

Kopiuj
private EmailFacade emailFacade = Mockito.mock(EmailFacade.class);
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);

A teraz jest ok, dzięki ;)

WE
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 5 lat
  • Postów:53
0

Warto w takich sytuacjach mockować sobie taką Fasadę czy może lepiej robić po prostu takie coś:

Kopiuj
    @Bean
    BetFacade betFacade(BetRepository betRepository, AccountFacade accountFacade, EventFacade eventFacade) {
        BetManager betManager = new BetManager(betRepository, eventFacade, accountFacade);
        return new BetFacade(betManager);
    }

    BetFacade inMemoryBetFacade(AccountFacade accountFacade, EventFacade eventFacade) {
        BetInMemoryRepository betInMemoryRepository = new BetInMemoryRepository();
        BetManager betManager = new BetManager(betInMemoryRepository, eventFacade, accountFacade);

        return new BetFacade(betManager);
    }

Fasada jako Bean do normalnego użytku i Fasada inMemory, która jest całkowicie niezależna od Springa?

edytowany 1x, ostatnio: weiss
Charles_Ray
Do testów jednostkowych, to akurat fajne podejście, również stosuję. Jedyny minus u kolegi to taki, że ta inMemory nie woła metody produkcyjnej - to się rozjedzie.
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

No dobra, tylko PO CO? o_O Czemu robisz jakieś dziwne new UserConfiguration().userFacade(new InMemoryUserRepository()); zamiast po prostu new UserFacade(new InMemoryUserRepository()) ? W ogóle niepotrzebnie kombinujesz tu z jakimiś adnotacjami...

Kopiuj
    private EmailFacade emailFacade = mock(EmailFacade.class);
    private UserFacade userFacade = new UserFacade(new InMemoryUserRepository(), emailFacade);

i voila, bez żadnych cudów na kiju.

A ten pomysł z robieniem w produkcyjnym kodzie dodatkowych wpisów w konfiguracji Springa, tylko po to żeby było pod testy to też bezsens.

No i @RunWith(MockitoJUnitRunner.class) i jednoczesnie MockitoAnnotations.initMocks(this); to jeszcze tylko jakiegoś @Rule ci brakuje. Widze ze lubisz mieć pewność że się coś wstrzyknie, to trzeba na 3 sposoby :D


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 3x, ostatnio: Shalom
baant
  • Rejestracja:ponad 11 lat
  • Ostatnio:3 miesiące
  • Lokalizacja:Wrocław
  • Postów:524
0

Obejrz te prezke Nabrdalika jeszcze raz, bo widze, że próbujesz to wprowadzić w życie, ale on to tam zrobił dużo sensowniej :)

S9
Jaka prezke?
baant
Nie pamiętam tytułu. Przyszedł z nią (i jeszcze jedną) na wrocJUGa około rok temu. Widać, że kolega oglądał bo to samo podejście zastosował, tylko troche gorzej :)
AP
https://www.youtube.com/watch?v=ILBX9fa9aJo&t=1s - o tym mowa, no i tak próbuje właśnie to zastosować. Niestety ze skutkiem takim jakim widać ;)
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Stacktrace
  • Postów:6822
0

EmailFacade powinno być interfejsem. Wtedy nie ma potrzeby mockowania, a jedynie trzeba napisać kadłubkową implementację pod testy. Mocki stosujesz gdy:

  1. chcesz zmieniać zachowanie mockowanego bytu w trakcie testu;
  2. chcesz zweryfikować interakcję pomiędzy SUT, a mockowanym bytem.

Każde inne zastosowanie mocków jest przerostem formy nad treścią.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
Shalom
Prawda jest taka że to zwykle lenistwo, szczególnie jak używasz tego tylko w jakimś jednym teście. Po co pisać sobie tą testową implementacje, skoro można to opędzić dwiema linijkami z mockito ;) Inna sprawa jak się to robi 50 razy w róznych testach.
hcubyc
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 3 lata
0
Shalom napisał(a):

No dobra, tylko PO CO? o_O Czemu robisz jakieś dziwne new UserConfiguration().userFacade(new InMemoryUserRepository()); zamiast po prostu new UserFacade(new InMemoryUserRepository()) ?

Przydatne gdy moduł jest spory, konstruktor przyjmuje dużo argumentów, a niektóre zależności są tworzone w tej metodzie userFacade.

Każde inne zastosowanie mocków jest przerostem formy nad treścią.

Można też leniwiej i zastosować stuba (mockito chyba też coś takiego potrafi?), ale interfejs też spoko


Limitations are limitless > ##### Ola Nordmann napisał(a)
> Moim językiem ojczystym jest C++ i proszę uszanować to, że piszę po polsku.
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Stacktrace
  • Postów:6822
1

@hcubyc: stub służy do zwracania (różnych) wartości w kontrolowany sposób. I tak zazwyczaj wykorzystywane jest mockito. mock pozwala na weryfikację interacji pomiędzy SUTem i mockowanym bytem.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
hcubyc
Wiem, dlatego napisalem że zamiast tworzyć prostą implementacje danego interfejsu można zastosowac stuba, gdy nie potrzeba weryfikacji między stubem, a SUTem
Koziołek
Tylko, że musisz do tego użyć mockito, a to jak widać jest problemem.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.