Mock class with static method to execute test case in Springboot - java

Hi I have a class with static method as below
#Log4j2
#Profile("!ut")
#Component('CALC')
#ConditionalOnProperty(name = 'CALC', havingValue = 'VAL')
public class
ClassA extends ClassMain {
public static org.apache.logging.log4j.Logger narrLogger;
#Autowired
IHCRefDataMgr ihcRefDataMgr;
public ClassA(IHCRefDataMgr ihcRefDataMgr) {
super(ihcRefDataMgr);
this.ihcRefDataMgr = ihcRefDataMgr;
}
public boolean calculate(msg) {
narrLogger = Narratives.getNarrativeLogger(msg)
}
}
I have another class where i trace the logs
#Data
#Component
#ConditionalOnProperty(name = 'CALC', havingValue = 'VAL')
#Log4j2
public class ClassB {
Boolean prepareMap() {
Boolean status = true;
try {
Set<String> securityKeys = ConcurrentHashMap.newKeySet();
ClassA.narrLogger.trace("Security Keys for given BA: {}", securityKeys);
// doing some processing
} catch (Exception e) {
status = false;
System.out.println(e);
}
}
}
Now I want to write a test case for ClassB, In my debugger when I come to ClassA.narrLogger, i get null pointer exception. How can i mock ClassA and allow it to pass assuming i dont want to test narrLogger.
Test Case
#Log4j2
#SpringBootTest
#TestPropertySource(properties = {"spring.profiles.active=test,ut", "calculator.name = ClassA", "eureka.client.enabled = false"})
public class ClassBTest {
ClassB classb;
#Test
public void prepareMapTest() {
var status = classb.prepareMap();
Assertions.assertEquals(true, status)
}
}
While running test case, it falls in exception as null pointer comes at ClassA.narrLogger

Related

AssertionFailedError while Testing Component with Configuration via Properties

Hi. I have a service and a util classes as follows:
#Service
#RequiredArgsConstructor
public class DeviceService {
private final DeviceUtil deviceUtil;
public String getAllDevicesDeviceType(DeviceType deviceType){
final String appIdByDeviceType = deviceUtil.getAppIdByDeviceType(deviceType);
// ... actually I return smth different, but for simplicity just returning appIdByDeviceType
return appIdByDeviceType;
}
}
#Component
public class DeviceUtil {
private final String androidAppId;
public DeviceUtil(ApplicationProperties applicationProperties) {
ApplicationProperties.DeviceClient client = applicationProperties.getDeviceClient();
androidAppId = client.getAndroidAppId();
}
public String getAppIdByDeviceType(DeviceType deviceType) {
if (deviceType == DeviceType.ANDROID) return androidAppId;
throw InvalidInputException.of(ErrorCode.PARAMETER_INVALID, ErrorMessage.DEVICE_TYPE_INVALID);
}
}
In ApplicationProperties class I am getting static data from application.yml file. While running project, all are ok. But when I wrote a test for DeviceUtil class as follows:
#ExtendWith(MockitoExtension.class)
class DeviceUtilTest {
#Spy
private ApplicationProperties applicationProperties;
#InjectMocks
private DeviceUtil deviceUtil;
#Test
void getAppIdByDeviceType_Should_ReturnSuccess() {
String actualResult = deviceUtil.getAppIdByDeviceType(DeviceType.ANDROID);
assertNotNull(actualResult);
// assertThat(actualResult).isEqualTo(APP_ID);
}
}
Test failed with foloowing description:
expected: not null
org.opentest4j.AssertionFailedError: expected: not <null>
How can I solve the issue? Thanks in advance.

Why doesn't my #Transactional method rollback when testing?

I have #transactional method that seems to be working (rolling back) if run the actual service and provide inputs that would cause a run-time error. If I create a Test for that method that throws a run-time error it doesn't seem to rollback anymore. Any idea why this doesn't work while testing?
it's somthing like:
#Service
public class SampleServiceImpl implements SampleService {
private final RepoA repoA;
private final RepoB repoB;
public SampleServiceImpl(RepoA repoA, RepoB repoB) {
this.repoA = repoA,
this.repoB = repoB
}
#Transactional
#Override
public void addItems() {
repoA.save(new ItemA(1,'name1')); //works
repoB.save(new ItemB(2,'name2')); //throws run-time error
}
}
#RunWith(SpringRunner.class)
#DataJpaTest
public class Tests {
#Autowired
private RepoA repoA;
#Mock
private Repob repoBMock;
#Test
public void whenExceptionOccurrs_thenRollsBack() {
var service = new SampleService(repoA, repoBMock);
Mockito.when(repoBMock.save(any(ItemB.class))).thenThrow(new RuntimeException());
boolean exceptionThrown = false;
try {
service.addItems()
} catch (Exception e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
Assert.assertFalse(repoA.existsByName('name1')); // this assertion fails because the the first item still exists in db
}
}
Just add annotation Rollback and set the flag to false.
#Test
#Rollback(false)

How to use dependency-injection with SpringBootTest

I have an application that uses SpringBoot for dependency injection and the app works fine, but testing fails because #Autowired fields aren't being injected during tests.
#SpringBootApplication
public class ProcessorInterface {
protected final static Logger logger = Logger.getLogger( ProcessorInterface.class );
public static void main(String[] args) {
try {
SpringApplication.run(ProcessorInterfaceRunner.class, args);
} catch (Exception ex) {
logger.error("Error running ProcessorInterface", ex);
}
}
}
#Component
#Configuration
#ComponentScan
public class ProcessorInterfaceRunner implements CommandLineRunner {
protected final static Logger logger = Logger.getLogger( ProcessorInterface.class );
#Autowired
private RequestService requestService = null;
#Autowired
private ValidatorService validatorService = null;
#Override
public void run(String... args) throws Exception {
ESPOutTransaction outTransaction = null;
outTransaction = new ESPOutTransaction();
// initialize outTransaction fields
...
// done initializing outTransaction fields
if (validatorService.isValid(outTransaction)) {
System.out.println(requestService.getRequest(outTransaction));
} else {
System.out.println("Bad Data");
}
}
}
#Service
public class ESPRequestService implements RequestService<ESPOutTransaction> {
#Autowired
ValidatorService validatorService = null;
#Override
public String getRequest(ESPOutTransaction outTransaction) throws IllegalArgumentException {
if (!validatorService.isValid(outTransaction)) {
throw new IllegalArgumentException("Invalid parameters in transaction object. " + outTransaction.toString());
}
StringBuffer buff = new StringBuffer("create request XML");
buff.append("more XML");
return buff.toString();
}
}
#Service
public class ESPValidatorService implements ValidatorService {
private static org.apache.log4j.Logger logger = Logger.getLogger(ESPValidatorService.class);
// declare some constants for rules
private static final int MAX_LENGTH_XYZ = 3;
#Override
public boolean isValid(OutTransaction outTransaction) {
ESPOutTransaction espOutTransaction = (ESPOutTransaction)outTransaction;
boolean isValid = true;
if (espOutTransaction == null) {
logger.warn("espOutTransaction is NULL");
isValid = false;
} else {
// XYZ is required
if (espOutTransaction.getXYZ() == null) {
logger.warn("XYZis NULL\r\n" + espOutTransaction.toString());
isValid = false;
}
// XYZ max length = MAX_LENGTH_XYZ
if (espOutTransaction.getXYZ() != null && espOutTransaction.getPubCode().trim().length() > MAX_LENGTH_XYZ) {
logger.warn("XYZis too long (max length " + MAX_LENGTH_XYZ + ")\r\n" + espOutTransaction.toString());
isValid = false;
}
}
return isValid;
}
}
These all work and I get good output when I run the app. When I try to test it though, it fails because it can't find ESPValidatorService to inject into ESPRequestService
#RunWith(Suite.class)
#SuiteClasses({ ESPOutTransactionValidatorTest.class, ESPRequestTest.class })
public class AllTests {}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ESPRequestService.class})
public class ESPRequestTest {
#Test
public void testGetRequest() {
ESPRequestService requestService = new ESPRequestService();
String XYZ = "XYZ";
ESPOutTransaction outTransaction = null;
outTransaction = new ESPOutTransaction();
outTransaction.setXYZ(XYZ);
String strRequest = "some expected request XML";
String request = requestService.getRequest(outTransaction);
assertEquals(request, strRequest);
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ESPValidatorService.class)
public class ESPOutTransactionValidatorTest {
#Test
public void testIsValid() {
ESPValidatorService validatorService = new ESPValidatorService();
ESPOutTransaction outTransaction = null;
// test request = null
assertFalse(validatorService.isValid(outTransaction));
String XYZ = "XYZ";
outTransaction = new ESPOutTransaction();
outTransaction.setXYZ(XYZ);
// test all good
assertTrue(validatorService.isValid(outTransaction));
// test XYZ
outTransaction.setXYZ(null);
assertFalse(validatorService.isValid(outTransaction));
outTransaction.setXYZ("ABCD"); // too long
assertFalse(validatorService.isValid(outTransaction));
outTransaction.setXYZ(XYZ);
}
}
How can I get the unit tests to auto wire?
I see two problems :
1) you don't rely on Spring beans but you create instances with the new operator.
Instead of writing :
ESPRequestService requestService = new ESPRequestService();
you should let Spring inject the instance :
#Bean
ESPRequestService requestService;
2) The #SpringBootTest configuration is not correct.
In each test, you specified a very specific bean class in the classes attribute of #SpringBootTest :
#SpringBootTest(classes = ESPValidatorService.class)
public class ESPOutTransactionValidatorTest {
and
#SpringBootTest(classes = {ESPRequestService.class})
public class ESPRequestTest {
But classes attributes of #SpringBootTest serves to specify the annotated classes to use for loading an ApplicationContext.
The annotated classes to use for loading an ApplicationContext. Can
also be specified using #ContextConfiguration(classes=...). If no
explicit classes are defined the test will look for nested
#Configuration classes, before falling back to a
SpringBootConfiguration search.
So all configuration classes and beans of your application may not be discovered and loaded in the Spring container .
To be able to load all application beans during your tests, the most simple way is not specifying the classes attribute in the #SpringBootTest annotation :
#SpringBootTest
public class ESPRequestTest { ...}
It will look for a Spring bean that holds the #SpringBootConfiguration.
Ideally, it will found the #SpringBootApplication bean of your application.
If the package of the test class is located inside the package (or at a lower level) of the #SpringBootApplication class, it should be automatically discovered.
Otherwise the other way is specifying a configuration that will allow to load all required beans :
#SpringBootTest(classes = MySpringBootApplication.class)
public class ESPRequestTest { ...}

Spring Autowired field null on Test Helper Class

Structure..
src/test/java
config
TestConfiguration.java
hooks
WebDriverHooks.java
nicebank
RunSteps.java
OtherSteps..
support
ATMUserInterface.java
KnowsTheDomain.java
#Autowired is correctly injecting KnowsTheDomain when placed Steps in nicebank package. But I am unable to #Autowired KnowsTheDomain when placed in Helper classes such as WebDriverHooks and ATMUserInterface
Does it require configuring annotation when autowiring to different packages? I am running Cucumber runner..
From WebDriverHook.java and ATMUserInterface.java, the field private KnowsTheDomain helper; is returning null instead of singleton instance. I need them to return what it returns when I run Steps in nicebank package.
Anyone has idea why this helper field is null?
TestConfiguration.java
#Configuration
#ComponentScan(basePackages = { "support"})
public class TestConfiguration {
#Bean
public static KnowsTheDomain knowsTheDomain() {
return new KnowsTheDomain();
}
}
WebDriverHooks.java
#ContextConfiguration(classes = TestConfiguration.class, loader=AnnotationConfigContextLoader.class)
#Configurable(autowire = Autowire.BY_TYPE)
public class WebDriverHooks {
#Autowired
private KnowsTheDomain helper;
#After
public void finish(Scenario scenario) {
try {
byte[] screenshot =
helper.getWebDriver().getScreenshotAs(OutputType.BYTES);
scenario.embed(screenshot, "image/png");
} catch (WebDriverException somePlatformsDontSupportScreenshots) {
System.err.println(somePlatformsDontSupportScreenshots.getMessage());
}
finally {
helper.getWebDriver().close();
}
}
}
RunSteps.java - this runs the Cucumber runner..
#RunWith(Cucumber.class)
#CucumberOptions(
plugin = {"pretty", "html:out"},
snippets = SnippetType.CAMELCASE,
features = "classpath:cucumber",
dryRun = false)
#ContextConfiguration(classes = TestConfiguration.class)
public class RunSteps {
}
KnowsTheDomain.java
#Component
public class KnowsTheDomain {
private Account myAccount;
private CashSlot cashSlot;
private Teller teller;
private EventFiringWebDriver webDriver;
public Account getMyAccount() {
if (myAccount == null) {
myAccount = new Account();
}
return myAccount;
}
public CashSlot getCashSlot() {
if (cashSlot == null) {
cashSlot = new CashSlot();
}
return cashSlot;
}
public Teller getTeller() {
if (teller == null) {
teller = new ATMUserInterface();
}
return teller;
}
public EventFiringWebDriver getWebDriver() {
if (webDriver == null) {
System.setProperty("webdriver.chrome.driver", "src/test/resources/chromedriver_win32/chromedriver.exe");
webDriver = new EventFiringWebDriver(new ChromeDriver());
}
return webDriver;
}
}
ATMUserInterface.java
#ContextConfiguration(classes = TestConfiguration.class, loader=AnnotationConfigContextLoader.class)
#Configurable(autowire = Autowire.BY_TYPE)
public class ATMUserInterface implements Teller {
#Autowired
private KnowsTheDomain helper;
#Override
public void withdrawFrom(Account account, int dollars) {
try {
helper.getWebDriver().get("http://localhost:" + ServerHooks.PORT);
helper.getWebDriver().findElement(By.id("Amount"))
.sendKeys(String.valueOf(dollars));
helper.getWebDriver().findElement(By.id("Withdraw")).click();
} catch (Exception e) {
System.err.println("err" + e);
}
}
}
#Autowired will work only in beans.
So make sure where are you using #Autowired annotation.
Add hooks package to #ComponentScan as below
#ComponentScan(basePackages = { "support", "hooks" })

Using autowired dependencies with certain mock dependency in Spring4

I have a rest resource for signup and login. both in a controller class. the controller class has a dependency to a service class with the business logic. the service class has further dependencies. cause i use an embedded db for testing, i want to use the real dependencies of my app instead to mock them with something like #injectmock #mock. there is only one certain dependency i have to mock. its the dependency for sending emails after a signup process. how to write test cases with #autowired function and one certain mock dependency for email notification?
#Controller
public class AccountCommandsController {
#Autowired
private LogoutService service;
#RequestMapping(value = "/rest/login", method = RequestMethod.POST)
public ResponseEntity login(#RequestBody Account account) {
AccountLoginEvent accountLoginEvent = service.loginAccount(new RequestAccountLoginEvent(account.getEmailAddress(), account.getPassword()));
if (accountLoginEvent.isLoginGranted()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else {
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
#RequestMapping(value = "/rest/signup", method = RequestMethod.POST)
public ResponseEntity signup(#RequestBody Account account) {
AccountSignupEvent signedupEvent = service.signupAccount(new RequestAccountSignupEvent(account.getEmailAddress(), account.getPassword()));
if (signedupEvent.isSignupSuccess()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else if (signedupEvent.isDuplicateEmailAddress()) {
return new ResponseEntity(HttpStatus.CONFLICT);
} else if (signedupEvent.isNoSignupMailSent()) {
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
} else {
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}
}
#Service
public class LogoutService {
#Autowired
private AccountsRepository accountsRepository;
#Autowired
private MailService mailService;
#Autowired
private HashService hashService;
public AccountSignupEvent signupAccount(RequestAccountSignupEvent signupEvent) {
if (accountsRepository.existEmailAddress(signupEvent.getEmailAddress())) {
return AccountSignupEvent.duplicateEmailAddress();
}
Account newAccount = new Account();
newAccount.setCreated(new Date());
newAccount.setModified(new Date());
newAccount.setEmailAddress(signupEvent.getEmailAddress());
newAccount.setPassword(signupEvent.getPassword());
newAccount.setVerificationHash(hashService.getUniqueVerificationHash());
SignupMailEvent mailSentEvent = mailService.sendSignupMail(new RequestSignupMailEvent(newAccount));
if (!mailSentEvent.isMailSent()) {
return AccountSignupEvent.noMailSent();
}
Account persistedAccount = accountsRepository.persist(newAccount);
return AccountSignupEvent.accountCreated(persistedAccount);
}
public AccountLoginEvent loginAccount(RequestAccountLoginEvent loginEvent) {
if (accountsRepository.existLogin(loginEvent.getEmailAddress(), loginEvent.getPassword())) {
return AccountLoginEvent.granted();
}
return AccountLoginEvent.denied();
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class LogoutTest {
private MockMvc mockMvc;
#Autowired
private AccountCommandsController controller;
#Before
public void setup() {
mockMvc = standaloneSetup(controller).build();
}
#Test
public void signupNoMail() throws Exception {
doReturn(AccountSignupEvent.noMailSent()).when(service).signupAccount(any(RequestAccountSignupEvent.class));
// when(controller.service.signupAccount(any(RequestAccountSignupEvent.class))).thenReturn(AccountSignupEvent.noMailSent());
mockMvc.perform(post("/rest/signup")
.content(new Gson().toJson(new Account(UUID.randomUUID().toString(), UUID.randomUUID().toString())))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isServiceUnavailable());
}
}
I hope you see the problem. Every dependency works fine instead mailservice. I dont want to use #injectmock and #mock with MockitoAnnotations.initMocks(this); in my test file, because of the neccessary to provide for all dependencies mocks.
if your dependencies are running and you have a configuration class where you have defined the endpoint, you can use ConfigurableApplicationContext class, something like this:
public class test {
private static ConfigurableApplicationContext appContext;
private LogoutService service;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
appContext = new AnnotationConfigApplicationContext(YourClassConfig.class);
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
}
Or you can re-write your endpoint with a configuration class and you can use WireMock (http://wiremock.org) to emulate your dependency with real data, this should be something like this:
public class test {
#Rule
public WireMockRule wireMockRule = new WireMockRule(15000);
private static ConfigurableApplicationContext appContext;
private LogoutService service;
private static String serviceMockUrl;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
serviceMockUrl = "http://localhost:" + wireMockRule.port();
appContext = new AnnotationConfigApplicationContext(TestConfig.class);
stubFor(get(urlEqualTo("urlToRequest")).
willReturn(aResponse().
withStatus(SC_OK).
withBody(createJsonArray("MapWithYourData").
withHeader("Content-Type", "application/json")));
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
#Configuration
static class TestConfig {
#Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer() {{
setProperties(new Properties() {{
setProperty("service.url", serviceMockUrl);
}});
}};
}
}
}
I hope this help you.
What you are trying to do is easily implemented using Spring Profiles.
On way to achieve it is the following:
#Configuration
public class TestConfiguration {
//this is the real mail service
#Bean
public MailService mailService() {
return new MailService(); //or whatever other bean creation logic you are using
}
//whatever else
}
#Configuration
#Profile("mockMail")
public class MockMailServiceConfig {
#Bean
#Primary
public MailService mockMailService() {
return mock(MailService.class);
}
}
Your test class would then look like:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
#ActiveProfiles("mockMail")
public class LogoutTest {
//do your testing
}
Note the use of #Primary in MockMailServiceConfig. I opted for this way since it wouldn't require you to introduce profiles anywhere else if you are not already using them. #Primary tells spring to use that specific bean if multiple candidates are available (in this case there is the real mail service and the mock service)

Categories

Resources