SpringRunner/JUnit running/testing a private (non-test) method - java

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. 🤦‍♂️

Related

What exactly is the purpose of SpringExtension?

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.

PowerMock static method mocking isn't taken into account for each #InjectMocks invocation

I want to write some unit tests, that use JUnit 4.12, Mockito 1.9.5 and PowerMock 1.6.1.
The class has some fields annotated with #Mock, as well as some fields annotated with #InjectMocks.
The attribute that is annotated with #InjectMocks reaches at some point a parent constructor which contains some static method invokation, that should be mocked with PowerMock.
The problem is the first test works seamlessly, while the second test does not seem to mock the static methods at all.
#RunWith(PowerMockRunner.class)
#PrepareForTest({ StaticClass.class })
public class TestClass {
#Mock
private SomeClass attribute1;
#InjectMocks
private SomeOtherClass attribute2;
#BeforeClass
public static void setUp() {
PowerMockito.mockStatic(StaticClass.class);
when(StaticClass.staticMethod(any(), any()).thenReturn(new SomeConcreteClass());
}
#Test
public void test1() {
assertEquals(attribute2.method1(), value1);
}
#Test
public void test2() {
assertEquals(attribute2.method2(), value2);
}
}
public class SomeOtherClass {
private SomeClass attribute;
public SomeOtherClass() {
SomeConcreteClass value = StaticClass.staticMethod(argument1, argument2);
value.someOtherMethod();
}
}
As mentioned before, the first test passes and the StaticClass.staticMethod() is mocked as expected by PowerMock.
The second test does not pass and it throws a NullPointerException at line when someOtherMethod is called on value (because value = null, as the StaticClass.staticMethod was not mocked anymore by PowerMock).
As explained in (Mocking behaviour resets after each test with PowerMock) Powermock resets the mocks before each test.
For some reason it works the first time - there exists
an unresolved bug report for that issue (https://github.com/powermock/powermock/issues/398).
Its arguably bad design, but a way to do what you want is the following:
Instead of relying on the annotation set up the mocks manually.
private SomeClass attribute;
private SomeOtherClass testClass;
#Before
public void setUp() {
PowerMockito.mockStatic(StaticClass.class);
Mockito.when(StaticClass.staticMethod(anyString())).thenReturn(new SomeConcreteClass());
attribute = Mockito.mock(SomeClass.class);
testClass = new SomeOtherClass();
// assign mock manually
testClass.attribute = attribute;
}
The prefered way would be to supply the attribute using the constructor of SomeOtherClass, however since you seem to use a empty constructor
you will have to set the value from the outside. If the attribute instance is not accessible you might be forced to use reflections.
The cleaner way would be to refactor the constructor of you SomeOtherClass to not use a static method inside. Instead passing SomeConcreteClass as a parameter to the constructor is the way to go.
Some people even say you should not have any logic inside of a constructor.

How to read variable from application.properties from method that is testing

I have class:
#Service
public class A {
#Value("${a.b.c}")
private String abc;
public void foo() {
sout(abc);
}
}
I Have test class:
#SpringBootTest
#SpringBootConfiguration
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:application.yml")
public class TestA {
#Value("${a.b.c}")
private String abc;
#InjectMocks
private A a;
#Test
public void testFoo() {
this.a.foo();
}
}
When I debugging the test method testFoo(),
I see that variable abc is read from the application.yml file.
But,
inside the foo() method,
I see that the variable abc is null.
How can I set variable abc such that it is available in method foo() when I trying to test this method?
Step one is to answer this question: Am I unit testing the code in my class or am I integration testing the combination of Spring and some collection of code that includes my class?
If you are unit testing your code,
then it is not necessary to have Spring do its thing.
Instead,
you only need to instantiate your class,
set the values that Spring would have set for you,
execute the method you are testing,
then verify that your method executed correctly.
Here is your example unit test rewritten as I suggested:
public class TestA
{
private static final String VALUE_ABC = "VALUE_ABC";
private A classToTest;
#Test
public void testFoo()
{
classToTest.foo();
}
#Before
public void preTestSetup()
{
classToTest = new A();
ReflectionTestUtils.setField(
classToTest,
"abc",
VALUE_ABC)
}
}
Some Notes:
ReflectionTestUtils is part of Spring-test.
You don't need to use #InjectMocks because you have no mocks to inject.
I don't know what sout is, so I excluded it from the test. You should verify that the sout method was called with the correct value (in this case VALUE_ABC).
If you are just unit testing your code, you don't need Spring, which means that you don't need to use the #RunWith annotation.
You can try to overide the properties like that:
#TestPropertySource(locations = "location.properties",
properties = "a.b.c=123")
Example taken from here

Using JMockit and Spring AOP together

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.

In a JUnit test is there a rule to set the subject of the test

I've been writing a lot of JUnit tests lately and see this same boilerplate pattern.
public class MathOpTest {
private MathOp a;
#Before
public void setUp(){
a = new MathOp();
}
...
}
Is there an annotation to set this up for me as I always need to write a setUp method and it usually only has the single class I'm testing.
Something like:
public class MathOpTest {
#TestSubject
private MathOp a;
...
}
You can assign the fields when they are declared:
public class MathOpTest {
private final MathOp mathOp = new MathOp();
...
}
This is simple and straight-forward, so I recommend that you assign fields in your test class at declaration time whenever possible (certainly in the case you gave).
If you want to understand a bit more, read on.
JUnit will create a unique instance of your test class for each test method, so even if your test modifies internal state of MathOp, using fields this way is safe as long as your tests don't modify global state.
For JUnit4-style tests (i.e. tests that do not extend junit.framework.TestCase) JUnit will create the test class just before the test method is run, and make it eligible for garbage collection after the test method completes.
Use #Before methods for more complex initialization.
Usually I use #Before when:
Initialization of the field is complex
Initialization of the field requires calling code that is declared to throw a checked exception
You need to do initialization after a #Rule has been applied (for instance, injecting a mock into a constructor)
Usually you would create the object under test in the test method when the class needs to be constructed different ways for different use cases.
Examples
Here is an example of using #Before and initMocks():
public class MathOpTest {
#Mock private Calculator mockCalculator;
#Mock private Supplier<Double> mockPreviousResultSupplier;
private MathOp mathOp;
#Before
public void createMathOp() {
MockitoAnnotations.initMocks(this);
mathOp = new MathOp(
mockCalculator, mockPreviousResultSupplier);
}
...
}
Here's an example of a #Before method that uses the result of a #Rule:
public class MyWriterTest {
#Rule public final TemporaryFolder folder = new TemporaryFolder();
private File output;
private MyWriter writer;
#Before
public void createMyWriter() {
output = folder.newFile();
writer = new MyWriter(output);
}
...
}
Aside: I personally wouldn't recommend using #InjectMocks to create the class you are testing. It's too much magic for my taste. Having an explicit constructor is cleaner and simpler, and I like my tests to be clear and simple :-)
Nothing like this directly exists in vanilla JUnit to my recollection. Most people elect to either initialize their test subject in a #Before statement, or inside of their tests. In its defense, it makes it clear what is being established before the tests are run, and it always resets the state of your test object.
If you're using Mockito, you actually do have the benefits of declaring a class and annotating it with #InjectMocks to both instantiate the class and inject whatever #Mock classes you had prior.

Categories

Resources