spring-boot and mockito test - why is my test failing? - unit-testing

In the application I'm working now, I need to implement a few tests and build upon them. I've been reading and trying out a few things, but haven't had much success.
The goal is to start back-filling with tests the service layer of the application. The first one to be covered is UserService.
So, my idea is to assert the test user we use on the application returns itself. The test class so far is:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest(classes = {ApplicationMain.class})
public class UserServiceTest {
#Mock
CentralData dataProviderMock;
#InjectMocks
private UserService userService;
private <project>User testUser;
private <project>User mockUser;
#Before
public void init() {
MockitoAnnotations.initMocks(CentralData.class);
System.out.println("dataProviderMock: " + dataProviderMock);
System.out.println("userService: " + userService);
userService = new UserService(dataProviderMock);
testUser = createTestUser();
}
private <project>User createTestUser() {
testUser = new <project>User();
testUser.setSystemId("testuser");
testUser.setEmailAddress("testuser#system.com");
testUser.setFirstName("Test");
testUser.setLastName("User");
// save user
userService.save(testUser);
return testUser;
}
#Test
public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
mockUser = userService.getUserById("testuser");
when(userService.getUser("testuser")).thenReturn(testUser);
assertEquals(testUser, mockUser);
}
}
On my UserService, I have this:
public UserService(CentralData dataProvider) {
this.dataProvider = dataProvider;
}
When I save the user, the mocked dataProviderMock logs to the console what I expect it to log. But on the test itself, the mockUser is always null.
I understand the userService does not really accesses the data layer and the database, so mockUser being null is not really wrong. So, how could I perform this test?
I'm pretty sure I'm missing something quite basic here, but can't really see it.

Related

***HELP*** Test/Mock: Service Class using Rest Template, Response and HTTP Entities, Exceptions

I have a service class, with for readability purpose, I have provided the code with dummy variables and objects. I am trying to write a JUNIT test class for the service, primarily with Mockito. No matter how hard I try, I am not able to hit the method serviceMethod irrespective of using spy/mock. I have also included a test, following the main class.
I know I am missing something here, but doesn't cross my mind. I need an eye to review this and let me know how I can write a proper test class for this and obtain coverage for the method.
(P.S. all the necessary imports are in-place and not pasted here to keep this concise)
Thanks in advance!
#Service
public class ServiceClass {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceClass.class);
#Autowired
String stringUrl;
RestTemplate restTemplate = new RestTemplate();
public void serviceMethod(ModelObject model) {
try {
HttpEntity<ModelObject> request = new HttpEntity<>(model);
ResponseEntity<String> response = restTemplate.exchange(stringUrl,
HttpMethod.POST, request, String.class);
LOGGER.info(response.getBody() + "and " + response.getStatusCode());
} catch (HttpClientErrorException exception) {
LOGGER.info(exception.getResponseBodyAsString());
} catch (HttpStatusCodeException exception) {
LOGGER.info(exception.getResponseBodyAsString());
}
}
Sample Test:
#RunWith(MockitoJUnitRunner.Silent.class)
public class ServiceClassTest {
#InjectMocks
private ServiceClass serviceClass;
#Mock
private RestTemplate restTemplate;
#Test
public void testServiceMethod() {
ServiceClass spy = Mockito.spy(serviceClass);
// ServiceClass spy = mock(ServiceClass.class, Mockito.CALLS_REAL_METHODS);
doNothing().when(spy).serviceMethod(Mockito.any(ModelObject.class));
Mockito.doCallRealMethod().when(spy).serviceMethod(Mockito.any(ModelObject.class));
ResponseEntity<String> responseEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
Mockito.when(restTemplate.exchange(ArgumentMatchers.anyString(), ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.<HttpEntity<ModelObject>>any(), ArgumentMatchers.<Class<String>>any()))
.thenReturn(responseEntity);
}

How to unit test a service call in xUnit and nSubstitute

I been trying to figure out how i can unit test service and so far have got nowhere.
I am using xUnit and NSubstitute (as advised by friends), below is the simple test that i want to run (which fails currently).
public class UnitTest1
{
private readonly RallyService _rallyService;
public UnitTest1(RallyService rallyService)
{
_rallyService= rallyService;
}
[Fact]
public void Test1()
{
var result = _rallyService.GetAllRallies();
Assert.Equal(2, result.Count());
}
}
My rally service class makes a simple call to the db to get all Rally entites and returns those:
public class RallyService : IRallyService
{
private readonly RallyDbContext _context;
public RallyService(RallyDbContext context)
{
_context = context;
}
public IEnumerable<Rally> GetAllRallies()
{
return _context.Rallies;
}
}
Any guidance would be appreciated.
Since you use .NET Core, I assume you also use Entity Framework Core. While it was possible to mock most of the operations in the previous EF version, however the EF Core suggests to use in-memory database for unit testing. I.e. you don't need to mock RallyDbContext, hence NSubstitute is not needed for this particular test. You would need NSubstitute to mock the service when testing a controller or application using the service.
Below is your Test1 written using in-memory database.
public class UnitTest1
{
private readonly DbContextOptions<RallyDbContext> _options;
public UnitTest1()
{
// Use GUID for in-memory DB names to prevent any possible name conflicts
_options = new DbContextOptionsBuilder<RallyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
}
[Fact]
public async Task Test1()
{
using (var context = new RallyDbContext(_options))
{
//Given 2 records in database
await context.AddRangeAsync(new Rally { Name = "rally1" }, new Rally { Name = "rally2" });
await context.SaveChangesAsync();
}
using (var context = new RallyDbContext(_options))
{
//When retrieve all rally records from the database
var service = new RallyService(context);
var rallies = service.GetAllRallies();
//Then records count should be 2
Assert.Equal(2, rallies.Count());
}
}
}
A working test application with this unit test is in my GitHub for your reference. I used SQL Express in the actual app.
I don't think it is standard to have a unit test constructor with a parameter. The unit test runner will new up this class, and unless you are using something that will auto-inject that parameter I think the test will fail to run.
Here is a standard fixture layout:
public class SampleFixture {
[Fact]
public void SampleShouldWork() {
// Arrange stuff we need for the test. This may involved configuring
// some dependencies, and also creating the subject we are testing.
var realOrSubstitutedDependency = new FakeDependency();
realOrSubstitutedDependency.WorkingItemCount = 42;
var subject = new Subject(realOrSubstitutedDependency);
// Act: perform the operation we are testing
var result = subject.DoWork();
// Assert: check the subject's operation worked as expected
Assert.Equal(42, result);
}
[Fact]
public void AnotherTest() { /* ... */ }
}
If you need a common setup between tests, you can use a parameterless constructor and do common initialisation there.
In terms of the specific class you are trying to test, you need to make sure your RallyDbContext is in a known state to repeatably and reliably test. You may want to look up answers specific to testing Entity Framework for more information.

#WithUserDetails does not seem to work

I have an application in which I use Spring Social Security for authentication and authorization. Unfortunately I am having some problems with mocking Spring Security. It seems that it does not work at all.
I have a REST controller that returns 404 Not Found if the identifier of the entity it should return is not available. If the user is not logged in then any page redirects to the social login page of my app.
I have read here that the #WithUserDetails annotation would suit me the best.
So my test method looks like this
#Test
#SqlGroup({
#Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD, statements = "INSERT INTO UserAccount(id, creationtime, modificationtime, version, email, firstname, lastname, role, signinprovider) VALUES (1, '2008-08-08 20:08:08', '2008-08-08 20:08:08', 1, 'user', 'John', 'Doe', 'ROLE_USER', 'FACEBOOK')"), })
#Rollback
#WithUserDetails
public void ifNoTeamsInTheDatabaseThenTheRestControllerShouldReturnNotFoundHttpStatus() {
ResponseEntity<String> response = restTemplate.getForEntity("/getTeamHistory/{team}", String.class, "Team");
Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
But this does not seem to work at all. It looks like the test method is executed with anonymous user, because the status I get is 200 OK.
My test class is annotated like this
#RunWith(SpringRunner.class)
#ActiveProfiles("dev")
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#Transactional
public class TeamRestControllerTest {
//...
}
Has anyone ever experienced such an issue with mocking Spring Security that is delivered by Spring Social?
I'm unable to test it at the moment, but here's a possible solution.
Looking at #WithUserDetails implementation:
#WithSecurityContext(factory = WithUserDetailsSecurityContextFactory.class)
public #interface WithUserDetails {
...
}
final class WithUserDetailsSecurityContextFactory implements
WithSecurityContextFactory<WithUserDetails> {
private BeanFactory beans;
#Autowired
public WithUserDetailsSecurityContextFactory(BeanFactory beans) {
this.beans = beans;
}
public SecurityContext createSecurityContext(WithUserDetails withUser) {
String beanName = withUser.userDetailsServiceBeanName();
UserDetailsService userDetailsService = StringUtils.hasLength(beanName)
? this.beans.getBean(beanName, UserDetailsService.class)
: this.beans.getBean(UserDetailsService.class);
String username = withUser.value();
Assert.hasLength(username, "value() must be non empty String");
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(
principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
You could create the Security Context of your choice following the same pattern:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
#WithSecurityContext(factory = WithoutUserFactory.class)
public #interface WithoutUser {
}
public class WithoutUserFactory implements WithSecurityContextFactory<WithoutUser> {
public SecurityContext createSecurityContext(WithoutUser withoutUser) {
return SecurityContextHolder.createEmptyContext();
}
}
The other available annotations: WithAnonymousUser, WithMockUser, WithSecurityContext (and WithUserDetails)
Adding my workaround, probably it can be helpful for someone else.
I think I met the same problem:
A #Testcontainers (for PostgreSQL DB emulation) + #SpringBootTest test.
Mocked the SecurityContext via annotation with #WithSecurityContext with mocking factory.
I need this mocking for an Envers RevisionListener, where I get the userName and userId from the SecurityContext created normally by Keycloak.
When calling the Spring beans in the test, mocking works ok.
But when calling the API via TestRestTemplate, SecurityContext is not mocked and is returning a null for all fields (principal, etc).
The original class looks like this:
#SpringBootTest(
classes = SpringBootInitializer.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"keycloak.enabled=false"}
)
#ContextConfiguration(
classes = PersistenceConfiguration.class,
initializers = MyTest.Initializer.class
)
// !!! the SecurityContext mocking will NOT work when calling the controller via REST
#MockKeycloakUser() // do not fail on getting Keycloak data in UserDataRevisionListener
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) // turn off Spring Security to avoid 401 and 302 responses
#Testcontainers // required to fill #Container fields with containers
#Log4j2
#ActiveProfiles("integration-test")
class MyTest {
#Autowired
private TestRestTemplate restTemplate;
// ...
// call via restTemplate looks like this
private List<MyDTO> executeSearchQuery(String query) {
String searchUrl = getSearchUrl(port, query, filter);
MyDTO[] results = this.restTemplate.getForObject(searchUrl, MyDTO[].class);
return List.of(results);
}
// ...
}
What I used to make the SecurityContext work is:
Add the MockMvc field to the test class.
Add #AutoConfigureMockMvc on the test class.
!!! Execute the API via MockMvc instead of TestRestTemplate
Looks like this:
// all other annotations on the test class stay the same
#AutoConfigureMockMvc // make MockMvc work
// ...
class MyTest {
#Autowired
private MockMvc mockMvc; // trick to make the mock SecurityContext work, which does not work when calling via TestRestTemplate
// Execute the API via mockMvc looks like this:
private String getApiResponse(MyRequest request, int expectedHttpStatus) {
final String url = getRequestUrl();
final String requestBody = JacksonUtils.serializeToString(request);
try {
final MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
.post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody)
;
// use MockMvc instead of TestRestTemplate to successfully use the mock user emulation
return mockMvc
.perform(builder)
.andExpect(status().is(expectedHttpStatus))
.andReturn()
.getResponse()
.getContentAsString(StandardCharsets.UTF_8);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
// ...
}

Robolectric: simulate network error in test

How is it possible to produce the same exception like during a real connection-error in robolectric tests?
I want to how the program acts if the network is currently not available. Is there a possibility to produce the same exception for my HttpClient?
I already tried:
Robolectric.getFakeHttpLayer().interceptHttpRequests(false); // with real network to a non existent IP
and
WifiManager wifiManager = (WifiManager) activity.getSystemService(Context.WIFI_SERVICE);
wifiManager.setWifiEnabled(false);
and
Robolectric.addPendingHttpResponse(404, null);
but none of them produces the same reactions like a real connection-loosing.
Thank you
I've checked Robolectric's FakeHttpLayer and haven't found way to simulate throwing an IOException.
So use mocking to make it working for you. First introduce HttpClientFactory (if you use HttpClient, you can use same approach for HttpUrlConnection):
public class HttpClientFactory {
public HttpClient createClient() {
return new DefaultHttpClient();
}
}
And now in your networking layer use factory instead of constructors (let for simplicity assume that it is synchronous):
public class HttpTransportLayer {
private final HttpClientFactory clientFactory;
public HttpTransportLayer() {
this(new HttpClientFactory());
}
// For tests only
HttpTransportLayer(HttpClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
public String requestData(String url) {
HttpClient client = factory.createClient();
...
}
}
So now you can in tests use Mockito:
HttpClient mockedClient = mock(HttpClient.class);
#Before
public void setUp() {
HttpClientFactory factory = mock(HttpClientFactory.class);
when(factory.createClient()).thenReturn(mockedClient);
target = new HttpTransportLayer(factory);
}
#Test
public void whenIOExceptionThenReturnNull() {
when(mockedClient.execute(any(HtptUriRequest.class))).thenThrow(new IOException());
String data = target.requestData("http://google.com");
assertThat(data).isNull();
}
That is dummy test and usually nobody will return null in case of error.
You could also task look to some dependency injection framework like Dagger to minimise injection code.
If you use any good framework for networking like Retrofit or Volley then it is even simpler - you don't need to mock anything and just invoke you error callback.
Hope it helps

Mockito: Verify if Spring Data JPA delete()-method is called

So, I am relatively new to unit-testing and especially mockito and am trying to figure out how to test the following scenario in Spring WebMVC:
This is my Service Class (simplified):
#Service
public class MyServiceImpl implements MyService {
#Resource
private MyCrudRepository myCrudRepository;
/**
* Method to delete(!) an entry from myTable.
*
*/
#Transactional
public void removeTableEntry(Long entryOid, String userId) throws Exception {
if (myCrudRepository.findOne(entryOid) != null) {
myCrudRepository.delete(entryOid);
log.info("User ID: " + userId + " deleted Entry from myTable with Oid " + entryOid + ".");
} else {
log.error("Error while deleting Entry with Oid: "+ entryOid + " from User with ID: " + userId);
throw new Exception();
}
}
}
Here I call the "built-in" delete-method of Spring Data JPA crudrepository, meaning no custom implementation in the repository itself (Using OpenJPA).
This is my simplified Test-Class:
#RunWith(MockitoJUnitRunner.class)
public class MyServiceImplTest {
final String USERID = "Testuser";
MyServiceImpl myService;
#Mock
MyCrudRepository myCrudRepository;
#Before
public void setUp() {
myService = new MyServiceImpl();
ReflectionTestUtils.setField(myService, "myCrudRepository", myCrudRepository);
}
//works (as expected? not sure)
#Test(expected = Exception.class)
public void testRemoveSomethingThrowsException() throws Exception {
doThrow(Exception.class).when(myCrudRepository).delete(anyLong());
myService.removeSomething(0l, USERID);
}
//does not work, see output below
#Test
public void testRemoveSomething() throws Exception {
verify(myCrudRepository, times(1)).delete(anyLong());
myService.removeSomething(0l, USERID);
}
//...
}
So, I try to verify that delete is called in testRemoveSomething(), but instead I get the following output:
Wanted but not invoked:
myCrudRepository.delete(<any>);
-> at myPackage.testRemoveSomething(MyServiceImplTest.java:98)
Actually, there were zero interactions with this mock.
And I'm nearly out of ideas why, to be honest (thinking about the #Transactional, perhaps? But this didn't get me to the solution, yet). May be that I'm completely wrong here (architectural, dunno) - if so, please feel free to give me a hint :)
It would be great to get some help here! Thanks in advance.
Your method fist calls findOne(), check if that returns something, and then calls delete(). So your test should first make sure that findOne returns something. Otherwise, the mock repository's findOne() method returns null by default. Moreover, you should verify that the call has been executed after it has been executed. Not before.
#Test
public void testRemoveSomething() throws Exception {
when(myCrudRepository.findOne(0L)).thenReturn(new TableEntry());
myService.removeTableEntry(0l, USERID);
verify(myCrudRepository, times(1)).delete(0L);
}
Also, you should use the #InjectMocks annotation rather than instantiating your service and injecting the repository using reflection.