What exactly SpringExtension do here? Even without that, the test case executes below as expected.
As per the doc doc
SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter programming model.
To use this extension, simply annotate a JUnit Jupiter based test class with #ExtendWith(SpringExtension.class), #SpringJUnitConfig, or #SpringJUnitWebConfig.
Unit Test with SpringExtension
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = SellCarService.class)
class SellCarServiceTest {
#Autowired
private SellCarService carService;
#Test
public void testGetCarId() {
String actual = carService.getCarDetails("1234");
String expected = "PRIUS";
Assertions.assertEquals(expected, actual);
}
}
Simple Unit Test
class SellCarServiceTest {
private final SellCarService carService = new SellCarService();
#Test
public void testGetCarId() {
String actual = carService.getCarDetails("1234");
String expected = "PRIUS";
Assertions.assertEquals(expected, actual);
}
}
If we want to use #Autowired in test classes, then we can go for the first approach or else we can use the simple approach as mentioned later. Is this understanding correct?
Or what is the advantage we get if we use SpringExtension or MockitoExtension?
You get no advantage of using the SpringExtension in this test class as you are not using any Mockito mocks.
You add #ExtendWith(SpringExtension.class) to the test class when you want to use the Mockito annotations such as #Mock, #Mockbean etc.
You are correct regarding #Autowired, there are also other ways to make autowiring work such as using the #SpringBootTest annotation.
Related
I have a test class that is annotated with #Spy and #InjectMocks and tested using Mockito. The class under test has a value (url) that is retrieved from the application.properties file. I'd like to test whether this url is being set correctly within the method that uses it. I can do this if I remove the #Spy and #InjectMocks annotations and use #Autowire and #SpringBootTest. However, that breaks other tests that use the spy functionality, so I'm just wondering if there's any way we can keep the spy working and test all our methods inside the same file, maybe without the need to bring in the #SpringBootTest annotation and the autowiring? The workaround we're using for now is to have two files, one that uses the spy and the other that tests the specific method to do with the properties file and that requires the full context to load, but not sure that's the best solution here?
Here is the test with the spy:
#ExtendWith(MockitoExtension.class)
class ProviderHelperServiceTest {
#Spy
#InjectMocks
ProviderHelperService providerHelperService;
#Value("${viaduct-url}")
String viaductUrl;
#Test
void testGetRequestBodyUriSpec() {
WebClient.RequestBodyUriSpec requestBodyUriSpec = providerHelperService.getRequestBodyUriSpec("sifToken");
final String[] url = new String[1];
requestBodyUriSpec.attributes(httpHeaders -> {
url[0] = (String) httpHeaders.get("org.springframework.web.reactive.function.client.WebClient.uriTemplate");
});
// Fails as url[0] comes back as null. Disabled and moved to another file.
assertEquals(viaductUrl, url[0]);
}
#SpringBootTest
class ProviderHelperService2Test {
#Autowired
ProviderHelperService providerHelperService;
#Value("${viaduct-url}")
String viaductUrl;
#Test
void testGetRequestBodyUriSpec() {
WebClient.RequestBodyUriSpec requestBodyUriSpec = providerHelperService.getRequestBodyUriSpec("sifToken");
final String[] url = new String[1];
requestBodyUriSpec.attributes(httpHeaders -> {
url[0] = (String) httpHeaders.get("org.springframework.web.reactive.function.client.WebClient.uriTemplate");
});
assertEquals(viaductUrl, url[0]);
}
}
And here is the method under test:
public class ProviderHelperService {
#Value("${viaduct-url}")
String viaductUrl;
public WebClient.RequestBodyUriSpec getRequestBodyUriSpec(String sifToken) {
WebClient.RequestBodyUriSpec requestBodyUriSpec = WebClient.create().post();
requestBodyUriSpec.header("Authorization", sifToken);
requestBodyUriSpec.uri(viaductUrl);
return requestBodyUriSpec;
}
}
The cleanest way to perform such tests is to replace field injection with constructor injection, and then you can quite easily confirm that the value that's passed into the class comes back out the service call.
If you're using Boot, it's usually best to replace use of #Value with #ConfigurationProperties. Depending on the specifics, you can either pass the whole properties object to the service's constructor or write an #Bean configuration method that unpacks the relevant properties and passes them as plain constructor parameters with new.
As I understand that if we use spring stereotypes then we don't need to use new keyword to create an instance. Spring manages that for us and provide us with the beans at runtime.
And in order for Spring to inject those beans we need to use #Autowired annotation where we want Spring to inject that bean.
Below I have a very simple class where I am using #Component so that spring manages that. This class has one List which I am initializing with my own responsibility and then a small method which does some logic.
#Slf4j
#Data
#NoArgsConstructor
#AllArgsConstructor
#Component
public class Parser {
private List<String> strList = new ArrayList<>();
public void parseStrings(final String[] strs) {
Arrays.stream(strs)
.map(String::toLowerCase)
.filter(str -> str.length() > 8)
.filter(str -> str.endsWith("sam"))
.forEach(sam1 -> { strList.add(sam1); });
}
}
I also wrote one unit test to test that and here is that.
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.*;
#RunWith(MockitoJUnitRunner.class)
class ParserTest {
#Autowired
private Parser parser;
#Test
void parseStrings() {
String str[] = {"abcsamsam", "abcsyjhgfed abdul sam","abcAhgbkgdjhul samad", "abcabjhgdulsamsam", "sa"};
parser.parseStrings(str);
assertTrue(parser.getStrList().size() == 3);
assertTrue(parser.getStrList().get(0).equalsIgnoreCase("abcsamsam"));
}
}
The test fails with
java.lang.NullPointerException when it tries to call parseStrings method which means that its not able to inject a proper initialized bean at run time.
Can some one guide that what I am missing?
Is it necessary to add constructors (which here I am doing using lombok annotations) when using spring stereotypes on a class.
I don't see any mock created so why you are using #RunWith(MockitoJUnitRunner.class)?
I've seen as well answers recommending the use of #SpringBooTest. This annotation loads the whole context of your application basically for integration tests in order to integrate different layers of the application. That also means no mocking is involved. Do you really need that? (I don't think so since you're talking about unit test)
If your parser doesn't reference any other Bean (which need to be mocked), then you are in case of simple unit test.
#RunWith(SpringRunner.class) // you can even removed it
class ParserTest {
private Parser parser;
#Before
public void setUp() {
parser = new Parser();
}
#Test
void parseStrings() {
String str[] = {"abcsamsam", "abcsyjhgfed abdul sam","abcAhgbkgdjhul samad", "abcabjhgdulsamsam", "sa"};
parser.parseStrings(str);
assertTrue(parser.getStrList().size() == 3);
assertTrue(parser.getStrList().get(0).equalsIgnoreCase("abcsamsam"));
}
Spring Autowire if you run the test case with SpringRunner. So modify the test class as follows.
#RunWith(SpringRunner.class)
class ParserTest {
}
To answer your second question,
No, it is not necessary to add no-argument constructor unless you also have a parameterised constructor in the same class. In that case you need to explicitly add a no-arg constructor.
Why so you even need MockitoJUnitRunner here? The Parser has no dependencies. A simple initialization in the test will be enough. Just inialize the Parser instead of using an annotation. #SpringBootTest is meant for integration tests. It brings in the Spring context and makes your unit test slow & bulky.
In my case the class was not declared public
This should work:
#SpringBootTest
#RunWith(SpringRunner.class)
class ParserTest {
#Autowired
private Parser parser;
#Test
void parseStrings() {
String str[] = {"abcsamsam", "abcsyjhgfed abdul sam","abcAhgbkgdjhul samad", "abcabjhgdulsamsam", "sa"};
parser.parseStrings(str);
assertTrue(parser.getStrList().size() == 3);
assertTrue(parser.getStrList().get(0).equalsIgnoreCase("abcsamsam"));
}
}
SpringBoot2,you can use the annotation:#SpringBootTest to do unit test.
just like below case:
#SpringBootTest
class DemoApplicationTests {
#Test
void contextLoads() {
}
}
We have a Spring 5 application using JUnit 4 as our test harness (w/ SpringRunner). We're experiencing an issue where a private helper method that's not marked with a #Test annotation is being run as a test. This happens in both IntelliJ and Maven.
The method signature is:
private Optional<TestSuiteJsonObject> createTestSuite(String name, TestType type) throws IOException, LicenseException {
And the test class itself looks like:
public class TestSuitesControllerTest extends ControllerTest
There are no annotations on either. The ControllerTest looks like:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = CompanyTestApplication.class)
#AutoConfigureMockMvc
#Ignore
public class ControllerTest {
...
}
The behavior is that these test methods are run with null arguments. We don't want them to be run at all. The rest of the methods in this class are appropriately marked with the #Test annotation.
I wondered if the fact that the word test is in the class/method name could be causing JUnit to identify it as runnable, but changing the names of both the class and method has no effect.
Adding #Ignore to the private method also has no effect!
What are we doing wrong? Did we step into an alternate dimension where test harnesses are actually testing stress responses of the engineers?
It was a silly mistake, but I'm leaving it for any future folk who find themselves in the same situation.
I had two methods with the same name. The test method:
#Test
#WithMockUser(username = "admin", roles = "ADMIN")
public void createSuite() throws Exception { ... }
And the helper method:
private static Optional<TestSuiteJsonObject> createSuite(String name, TestType type) { ... }
And we somehow glossed over this. 🤦♂️
Suppose I have a program that looks like this:
#Component
public class MainAction {
public void doTheAction() {
System.out.println("Now doing the action");
}
}
#Aspect
#Component
public class BeforeAspect {
#Autowired
private Logger logger;
#Before("execution(* thepackagename.MainAction.*(..))")
public void doBefore() {
logger.log("The #Before advice has run");
}
}
#Component
public class Logger {
public void log(String s) {
System.out.println(s);
}
}
This is working fine if I run it through Eclipse (the main method esentially calls mainAction.doTheAction() after mainAction is created by Spring).
Now I want to write a test that ensures that the log method is called correctly when doTheAction is called. We're using JMockit for our testing. (This is a very simplified case of a problem I'm actually facing; a more complex logger is being called via an AOP aspect, and the wrong value of something is being logged. Before working on a fix, I'm trying write a test to ensure the logged value is correct.)
This is what my (simplified) test currently looks like:
#RunWith(JMockit.class)
#ContextConfiguration(locations = {"classpath:Beans.xml"})
public class MainActionTest {
#Tested
private MainAction mainAction;
#Test
public void testThatLoggerIsCalled(#Injectable Logger logger) {
new Expectations() { {
logger.log(anyString);
} };
mainAction.doTheAction();
}
}
The #ContextConfiguration may be useless. Earlier I had tried #RunWith(SpringJunit4ClassRunner.class), which is why #ContextConfiguration is there, but none of the mocking stuff was handled. Also, I'm using #Tested and #Injectable instead of #Autowired and #Mocked, following the suggestion in this question; without that, mainAction remained null. So now the test runs, and Now doing the action appears in the output. But The #Before advice has run doesn't appear (and doesn't appear even if I don't mock the Logger), and the expectation fails.
How can I use JMockit and AOP together?
Edit: As requested, I added something to print the classpath property. Here it is (with unimportant parts of some path names removed):
Eclipse workspaces\springtest8\target\test-classes
Eclipse workspaces\springtest8\target\classes
C:\eclipse\plugins\org.junit_4.11.0.v201303080030\junit.jar
C:\eclipse\plugins\org.hamcrest.core_1.3.0.v201303031735.jar
.m2\repository\org\jmockit\jmockit\1.18\jmockit-1.18.jar
.m2\repository\junit\junit\4.11\junit-4.11.jar
.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar
.m2\repository\org\springframework\spring-context\4.2.0.RELEASE\spring-context-4.2.0.RELEASE.jar
.m2\repository\org\springframework\spring-aop\4.2.0.RELEASE\spring-aop-4.2.0.RELEASE.jar
.m2\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar
.m2\repository\org\springframework\spring-beans\4.2.0.RELEASE\spring-beans-4.2.0.RELEASE.jar
.m2\repository\org\springframework\spring-core\4.2.0.RELEASE\spring-core-4.2.0.RELEASE.jar
.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar
.m2\repository\org\springframework\spring-expression\4.2.0.RELEASE\spring-expression-4.2.0.RELEASE.jar
.m2\repository\org\aspectj\aspectjrt\1.8.6\aspectjrt-1.8.6.jar
.m2\repository\org\aspectj\aspectjweaver\1.8.6\aspectjweaver-1.8.6.jar
.m2\repository\org\springframework\spring-test\4.2.0.RELEASE\spring-test-4.2.0.RELEASE.jar
.m2\repository\javax\inject\javax.inject\1\javax.inject-1.jar
/C:/eclipse/configuration/org.eclipse.osgi/bundles/201/1/.cp/
/C:/eclipse/configuration/org.eclipse.osgi/bundles/200/1/.cp/
Edit 2: I got things to work by removing JUnit4 from the Libraries tab in Configure Build Path.
The following test works fine, using Spring 3.0 or newer (tested with Spring 3.0.7, 4.0.5, and 4.2.0):
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:beans.xml")
public class MainActionTest
{
#Inject MainAction mainAction;
#Test
public void testThatLoggerIsCalled(#Mocked final Logger logger)
{
mainAction.doTheAction();
new Verifications() {{ logger.log(anyString); }};
}
}
I never had to annotate JUnit tests with RunWith to use JMockit. From the documentation you need to make sure the jmockit jar is loaded before junit's or add the javaagent jvm parameter. That way you'll be able to run the tests with Spring's Junit Runner and have JMockit as the mock framework.
I have a class which I am trying unit test. I am trying to test it using Mockito, and to resolve the spring injection with mockito I am using Springockito and Springockito-annotations.
#ContextConfiguration(loader = SpringockitoContextLoader.class,
locations = {"classpath:testApplication-context-EU.xml"})
public class RelatedSearchToHotUrlProcessorTest extends AbstractJUnit4SpringContextTests {
#Autowired
RelatedSearchToHotUrlProcessor processor;
#ReplaceWithMock
private RestOperations restTemplate;
#Test
public void testGetCategoryFromIdWithNoStoredAlias() {
Taxonomy mockTaxonomy = mock(Taxonomy.class, RETURNS_DEEP_STUBS);
GraphType.Node mockNode = mock(GraphType.Node.class);
when(restTemplate.getForObject(anyString(), eq(Taxonomy.class))).thenReturn(mockTaxonomy);
when(mockTaxonomy
.getRev()
.get(0)
.getCountry()
.get(0)
.getGraph()
.getNodeOrAtom()
.get(0)).thenReturn(mockNode);
when(mockNode.getAlias()).thenReturn("mockalias");
String categoryAlias = processor.getCategoryAliasFromId(13130L);
assertEquals("mockalias", categoryAlias);
}
}
If I remove the #ReplaceWithMock and the private RestOperations restTemplate lines then it makes the right call and the value can be validated as correct. However, I want to mock the RestOperations object inside the processor, but using the #ReplaceWithMock makes the restTemplate variable null, causing it to fail. I haven't been able to work out how to isolate this member and mock it.
Was having a similar problem, I found that annotating with #WrapWithSpy or #ReplaceWithMock was not enough. That is to say the field in the test class was null. Adding the #Autowired annotation in addition to the springockito annotation as per Arasu's comment fixed the problem - although it does look weird...
#Autowired
#WrapWithSpy
private SomeBean someBean;