We use Spring's #DirtiesContext annotation quite liberally throughout our test code to guarantee no side effects between test classes. I recently learned about TestNG's #DataProvider and #Factory and some test in my code base would really benefit from this approach.
However, I found that #DirtiesContext causes the Spring beans to be shutdown too early, namely after the last test method of the first test instance the factory created.
A broken down version of my current setup looks like this
#ContextConfiguration(classes = { SomeSpringContex.class })
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
class TestClass {
#Autowired
Database database;
private final String configParameter;
#Factory(dataProvider = "testSetup")
public TestClass(String configParameter) {
this.configParameter = configParameter;
}
#DataProvider(name="testSetup")
public static Object[][] createParameters() {
return new Object[][] {
{ "param1" },
{ "param2" }
};
}
#BeforeMethod
public void useConfigParameter() {
// use configParameter to set up test
}
#Test
public void firstTest() {
database.query("SELECT * FROM foo");
}
#Test
public void secondTest() {
database.query("SELECT * FROM bar");
}
}
This essentially creates two instances of TestClass, one set up with "param1" and one set up with "param2". Thus every test is run twice, as intended, and the output (in eclipse) looks something like this
firstTest (with "param1") => SUCCESS
firstTest (with "param2") => SUCCESS
secondTest (with "param1") => SUCCESS
secondTest (with "param2") => FAIL, because the Database bean, along with it's database connection, was shutdown already
When I remove the #DirtiesContext annotation the test methods all work fine, but in my actual tests I run into inter test problems. Is there a way to make #DirtiesContext and #Factory play nicely together?
Related
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. 🤦♂️
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
Hello every one I need to write unit tests for my methods. I'm having a bit of trouble because I'm new to JUnit. I need to write a test for this method.
this is my method
#Override
public Long countSellingOrdersInQueue(String principal) {
List<String> states = Arrays.asList(PENDING.name(), REGULARIZED.name());
return orderRepository.countByArticleUserIdAndStateIn(principal, states);
}
I try but i'm blocked and this is my result
P.S. test is passed but I don't understand if my test is true
#MockBean
private OrderRepository orderRepository;
private String principal ;
#Test
public void countSellingOrdersInQueueTest(){
orderService.countSellingOrdersInQueue(principal);
List<String> states = Arrays.asList(PENDING.name(), REGULARIZED.name());
orderRepository.countByUserIdAndStateIn(principal,states);
}
In your case, it is just the unit test, you need not use #MockBean, as it loads the context. Unit tests are meant to be run faster, using #MockBean, will load the context and takes time to complete the test. Here is the suggestion of when to use #Mock and when to use #MockBean.
As Maxim said, there were no assertions in the test. That was the reason why the tests weren't failing.
Few things to keep in mind while writing the test.
Tests are considered as documentation for the code, it should be more readable in such a way it makes others to understand the code.
As said before, unit tests are for giving faster feedback
Have AAA (Arrange, Act, Assert) structure in tests. More info here
Here is the code:
public class OrderServiceTest {
#InjectMocks
private OrderService orderService;
#Mock
private OrderRepository orderRepository;
#Before
public void setUp() throws Exception {
initMocks(this);
}
#Test
public void countSellingOrdersInQueueTest(){
when(orderRepository.countByArticleUserIdAndStateIn(any(), any())).thenReturn(1L);
String principal = "dummyString";
Long actualCount = orderService.countSellingOrdersInQueue(principal);
List<String> expectedStates = Arrays.asList("State 1", "State 2");
assertThat(actualCount, is(1L));
verify(orderRepository).countByArticleUserIdAndStateIn(principal, expectedStates);
}
}
Test passes because you don't have any assertion, which checks result. You just invoke methods which executes without exception.
Simple test example:
#Test
public void test() {
assertEquals(true, true);
}
In your case test will be look likes:
#Test
public void countSellingOrdersInQueueTest(){
orderService.countSellingOrdersInQueue(principal);
List<String> states = Arrays.asList(PENDING.name(), REGULARIZED.name());
orderRepository.countByUserIdAndStateIn(principal,states);
assertEquals(10, orderRepository.countByUserIdAndStateIn(principal,states));//10 replace to expectetion count
//add some more checks
}
In my test class, suppose I've 15 test cases. Out of 15, I require common test data for only 5 test cases. Hence I want to write a method, which will create test data, but that method will execute before any of those 5 tests are run.
I know #BeforClass - which will run before any of tests from the class is run and #BeforeMethod - which will run before every test in the class.
I do not want to use #BeforeClass to create test data for 5 test cases out of 15 because if I want to debug a test which does not belong to those 5 test cases still it will create data, which is not required for my current test, also it will increase execution time.
Is there any way with TestNG, I can run specific method before some of the tests are executed(Without using testng.xml)
TestNG only provides dependency on other test methods. which makes a method to make as a test method.
To Archive what you needed You can do This:
#Test
void testMethod(){
//this is your test method
beforemethod();
}
//your before method for you test-case
void beforemethod(){
}
Hope this fixes you issue
From your description I understand you need a dataProvider (which is exactly that, a method providing same data for Multiple test cases or alternatively multiple data for the same test case).
#DataProvider(name = "dataProviderFor5TestCases")
public Object[][] createData() {
return new Object[][] {
{ "Joe", new Integer(43) },
{ "Mary", new Integer(32)},
};
}
Then you can declare the dataProvider on your test case as such:
#Test(dataProvider = "dataProviderFor5TestCases")
public void testCase1(String name, Integer age) {
System.out.println(name + " " + age);
}
Result will be:
Joe 43
Mary 32
So testCase1 will be executed twice with the set of data created in the dataProvider. However, I think you need the same data for all 5 test Cases (achievable).
Now, regarding execution time. I am not 100% sure but I believe data is created on demand (i.e. if the testCase is skipped or failed no data is created; but I had a very small load so please try it and let us know!)
Update after OP's comment:
So, you are probably better off, using testGroups then which will suit you for both setup before the test and cleanup afterwards (without being invoked for irrelevant test cases):
#Test(groups = { "init" })
public void serverInit() {
startServer();
}
#Test(groups = { "init" })
public void initEnvironment() {
createUsers()
}
#Test(groups = { "cleanup"}, dependsOnGroups = { "init.*" })
public void testCase1() {
//perform your tests
}
#Test(dependsOnGroups = { "cleanup"})
puplic void cleanup(){
deleteUsers();
killServer();
}
The above testCase1 won't be executed if any of the init test method fail (i.e. server fails to start). In addition, cleanup method will only be invoked if the testCase1 succeeded. If you want the cleanup method to be run regardless of testCase1 result you can use alwaysRun like so:
#Test(dependsOnGroups = { "cleanup"}, alwaysRun=true)
Hope that helps!
Best of luck!
Example taken from here:
TestNG DataProvider
You can use dependsOnMethods in #Test() annotation
e.g.
#Test
public void testDataSetup()
{
// Setup your testDataHere
}
#Test(dependsOnMethods = { "testDataSetup" })
public void testExecute1()
{
// Use Your logic here which executes after datasetup
}
For Complete Tutorials see this link
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.