Zamockowany obiekt zwraca nullpointera

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:

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

@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

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

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

@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.

@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ąć?

1

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

0

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

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

A teraz jest ok, dzięki ;)

0

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

    @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?

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...

    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

0

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

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ą.

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

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.

1 użytkowników online, w tym zalogowanych: 0, gości: 1