Executing extension before SpringExtension - spring

I'm trying to implement integration testing in my app and have test class like that:
#ExtendWith(value={MyDockerExtension.class})
#ExtendWith(value={SpringExtension.class})
#WebAppConfiguration
#ContextConfiguration(classes={...})
#TestInstance(TestInstance.LifeCycle.PER_CLASS)
public class TestClass{ ... }
Is there any way to make MyDockerExtension execute some code, before whole SpringExtension start working and generate whole Context with Configurationc classes?
I've heard that order in which we declare extensions is the key, but sadly MyDockerExtension that implements BeforeAllCallback, AfterAllCallback executes right before test method and after whole context is loaded. In that situation it's to late to start containers with docker, becuase since whole context is loaded my app already tried to connect to the container.

At first I was skeptical about the order being fixed but you're correct:
Extensions registered declaratively via #ExtendWith will be executed in the order in which they are declared in the source code.
Regarding the MyDockerExtension, you may want to look at the extension point TestInstancePostProcessor, which is called before #BeforeAll. SpringExtension implements it and I guess it's there where it sets up the application context. If you also implement it, you should be able to act before it does.

Related

Trouble using a base Spock Specification and JenkinsRule with PluginManager

I want to use different sets of plugins for different Spock Specifications that extend an abstract BaseSpecification. I am having trouble doing that. From what I read, the #WithPlugin annotation didn't quite seem like what I need. So I've been trying to use JenkinsRule.with(PluginManager).
I'm using the Groovy Spock framework and v2.59 of the Jenkins Unit Test Harness org.jenkins-ci.main:jenkins-test-harness:2.59 to test Jenkins Job DSL that we've written to automate the initialization of our Jenkins instances.
I have a BaseSpecification class where I declare a field #Shared #ClassRule jenkinsRule = new JenkinsRule(). As I understand it, the JenkinsRule is how we interact with the Jenkins Unit Test Harness and the embedded Jenkins instances. This has worked fine in the past for all Specifications that extend BaseSpecification and define their own feature methods.
But when I try to use something like #Shared #ClassRule jenkinsRule = new JenkinsRule().with(getPluginManager()), in the abstract BaseSpecification—where abstract JenkinsRule getPluginManager() is declared—if I return MyPluginManager.INSTANCE for some specs but not others, I keep getting the following error:
class org.jenkinsci.plugins.workflow.job.WorkflowJob is missing its descriptor
java.lang.AssertionError: class org.jenkinsci.plugins.workflow.job.WorkflowJob is missing its descriptor
at jenkins.model.Jenkins.getDescriptorOrDie(Jenkins.java:1600)
at org.jenkinsci.plugins.workflow.job.WorkflowJob.getDescriptor(WorkflowJob.java:421)
at hudson.model.ItemGroupMixIn.createProjectFromXML(ItemGroupMixIn.java:285)
at jenkins.model.Jenkins.createProjectFromXML(Jenkins.java:3989)
at javaposse.jobdsl.plugin.JenkinsJobManagement.createNewItem(JenkinsJobManagement.java:517)
at javaposse.jobdsl.plugin.JenkinsJobManagement.createOrUpdateConfig(JenkinsJobManagement.java:141)
at javaposse.jobdsl.dsl.AbstractDslScriptLoader.extractGeneratedJobs_closure4(AbstractDslScriptLoader.groovy:204)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at javaposse.jobdsl.dsl.AbstractDslScriptLoader.extractGeneratedJobs(AbstractDslScriptLoader.groovy:197)
at javaposse.jobdsl.dsl.AbstractDslScriptLoader.extractGeneratedItems(AbstractDslScriptLoader.groovy:184)
at javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScripts_closure1(AbstractDslScriptLoader.groovy:63)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScripts(AbstractDslScriptLoader.groovy:46)
at BaseJobScriptsSpec.generateDslItems(BaseJobScriptsSpec.groovy:241)
at BaseJobScriptsSpec.setupSpec(BaseJobScriptsSpec.groovy:38)
It seems like whichever PluginManager is used first changes state somewhere, either in a temporary file directory or elsewhere, that causes the error on the next test that uses a JenkinsRule with a different PluginManager. As it is now MyPluginManager is just a copy/paste of org.jvnet.hudson.test.TestPluginManager while I get this proof of concept to work.
What am I doing wrong here? Thank you.
EDIT 2021-08-04 Adding link to repo with reproduced error:
https://github.com/matthiasdenu/plugin-manager-bug/blob/main/src/test/groovy/CustomPluginMangerSpecification.groovy#L12

Reset Spring-Boot During Integration Tests

I guess am trying to get a corner case to work here. In my current project there are about 20 integration tests. One new integration test requires #EnableAsync to make the test work:
#RunWith(SpringRunner.class)
#EnableAsync
#SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class MyITest {
:
}
When run alone, this test works fine.
Considering Maven and Eclipse' execution of tests in one project and knowing that the environment is only created once and reused (or soft-reset) for all integration tests, it's somewhat a requirement that this integration test runs first. However, that's (nearly?) never the case.
Therefore, this integration test (nearly?) always fails. One obvious solution is to add #EnableAsync to all integration tests. However, that's a bad dependency which I bet is broken once somebody adds another integration test and forgets this requirement.
I'm looking for a way to force the SpringRunner to completely reset the context and really start it from scratch also looking at #EnableAsync. Ideally that way includes to flag that SpringRunner has to reset the context (i.e., remove the #EnableAsync) after the test, too. That way any order of execution would ensure that only that very one test has the #EnableAsync.
Is there a way to do this? Or can I manually turn on/off the async-stuff by code in a #Before/#After method?
take a look at DirtiesContext
Not sure if this is what you're looking for.
Possible duplicate of: How do you reset Spring JUnit application context after a test class dirties it?
Whow, I think I just found out by accident... What I have now:
#RunWith(SpringRunner.class)
#EnableAsync
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
ClassWithAnAutowiredAsyncDependency.class // <=== difference!!! ===>
})
public class MyITest {
:
#Autowired
private ClassWithAnAutowiredAsyncDependency mine;
:
}
It seems as if the given classes are reset (specially?) or at least the autowiring happens in there again or something. I can't explain it any different.
I'm sure that this integration test is not the first integration test being run and still the asynchronous bit seems to be in place.
Well, test is green, it works...

What are advantages of using #ContextHierarchy over pure #ContextConfiguration

Hi I don't understand what advantages give me usage of #ContextHierarchy like below:
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("FirstTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
...
}
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("SecondTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
...
}
over usage of single #ContextConfiguration with locations argument, like below:
#ContextConfiguration(locations = {"classpath:test-db-setup-context.xml", "FirstTest-context.xml", "SecondTest-context.xml" })
In each case, application contexts are shared across diffrent junit test classes.
The difference is that beans in each context within the context hierarchy can't see beans in the other the context. So you can isolate different parts of your item under test.
An imortant thing to note here is that In case of #ContextHierarchy we get SEPARATE contexts that have SEPARATE life-cycles (initialization, shutdown). This is important because for example they can fail independently.
A practical example from my yard. We have a Spring application that communicates with some external services. We wanted an E2E test that starts these dependent services and the runs the tests. So we added an initializer to our #ContextConfiguration:
#ContextConfiguration{classes = TheApp.class, initializers = DockerInitializer.class}
public class TheAppE2ETests {
// ...
}
The initializer was preparing the external services (starting Dockers), customizing the properties so that The App can run and attaching to the close context event so that the Dockers could be cleaned up. There was a problem with this approach when The App context was failing to load (e.g. due to a bug):
After a failed initialization the ContextClosedEvent is not fired - the dockers were not stopped and cleaned up.
When the context fails to load the initializer is called over and over for every test which is run (not only for every test class - for every test method!).
So the tests kept on killing our CI environment every time a bug in The App's context caused the initialization to fail. The containers for the dependent services were started for every single test method and then not cleaned up.
We ended up using #ContextConfiguration and having two separate contexts for dockers and The App itself. This way in case of an above-mentioned situation the dockers are started in a separate context and therefore live there own live and can even be shared across multiple Spring tests (due to Spring's context caching mechanism).

Testing spring repositories

In the Spring Data I have found very helpful interface called JpaRepository. Because I need more functionality I decided to create my own interface of repository:
public interface BaseRepository<T, ID extends Serializable>
extends JpaRepository<T, ID> {
public <TA, TV> int deleteBy(SingularAttribute<T, TA> attr, TV val);
}
As you can see this is a generic interface. It works fine, but I would like to know how I can test it? Of course I can write integration test for each concrete repository but I am looking for better way.
As usual with testing, you should make sure you know what you're testing. Find answers to these questions:
Do you want to test the underlying database?
Do you want to test the Spring Data repository connector for this respository?
Do you want to test whether your code calls the correct methods on the interface?
Doing #1 is useless: The database vendor has already run thousands of tests on its product. There is rarely a reason to do this effort again.
Doing #2 is useless unless you suspect a bug in the code for Spring Data.
Which leaves us with #3. Use a mocking framework to make sure the method is called at the appropriate places (and maybe check the arguments, too).
That way, you can make sure your code behaves correctly.
If you notice the framework throwing errors or you notice that objects aren't deleted correctly, you can add more tests. But most of the time, this won't happen because of bugs in the database or Spring Data. Instead, your code won't call deleteBy() or it will call the method with the wrong arguments.

Acceptance testing preloading of data into GAE dev server datastore

In my application I have a set of of DAOs which I inject into my application layer. For an acceptance test I'm writing, I want to preload the dev_server datastore with data, so I use the same Spring config in my JUnit test (using the #ContextConfiguration annotation) to inject an instance of the relevant DAO into my test. When I actually go to store some data eg:
dao.add(entity)
I get the dreaded "No API environment is registered for this thread."
Caused by: java.lang.NullPointerException: No API environment is registered for this thread.
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppId(DatastoreApiHelper.java:108)
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:118)
....
This is probably because my test case hasn't read in the GAE application-web.xml with the app details (although I'm guessing here I could really be wrong); so it doesn't know to write to the same datastore that the app running on the dev_server is reading/writing to.
How can I get my test to "point" to the same datastore as the app? Is there some "datasource" mechanism that I can inject both into the app and the test? Is there a way to get my test to force the datastore api to read the needed config?
Here is a page that talks about how to do unit tests that connect to a dev datastore. Is this the kind of thing you're looking for? Basically it talks about two classes, LocalServiceTestHelper and LocalDatastoreServiceTestConfig that you can use to set up an environment for testing. While the example given is for unit tests, I believe it will also work for your situation.
You can then configure things like whether the dev datastore is written to disk or just kept in memory (for faster tests). If you want this data to go to the same place as your dev server, you will probably want to adjust this, as I think the default is the "in memory" option. If you look at the javadoc there is a "setBackingStoreLocation" method where you can point to whatever file you want.
I've found the solution!!!!
For some reason the Namespace, AppID and the AuthDomain fields of the test datastore have to match that of the dev_server, then the dev_server can see the entities inserted by the test.
You can see the values for the environment (dev_server or test code) with the following statements
System.out.println(NamespaceManager.get());
System.out.println(ApiProxy.getCurrentEnvironment().getAppId());
System.out.println(ApiProxy.getCurrentEnvironment().getAuthDomain());
In your instance of LocalServiceTestHelper (eg: gaeHelper), you can set the values for the test environment
// the NamespaceManager is thread local.
NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
gaeHelper.setEnvAppId(<the name of your app in appengine-web.xml>);
gaeHelper.setEnvAuthDomain("gmail.com");
Then the dev_server will see your entities. However because of synchronisation issues, if the test writes to the datastore after the dev_server has been started the dev_server wont see it unless it can be forced to reread the file (which I haven't figured out yet). Else the server has to be restarted.
I've found a workaround, although it's not very nice because each test method doesn't clean up the Datastore, as explained in the article Local Unit Testing for Java, however, the Datastore starts clean each time the Test class is run, so it's not so bad, provided that you're careful about that.
The problem is, that when using SpringJUnit4ClassRunner, the spring environment is created before the #Before annotation can be run, the solution is use #BeforeClass and use a static variable for LocalServiceTestHelper, to have them created before the Spring Environment is set up.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:META-INF/spring/context-test.xml")
#Transactional
public class MyTest {
#Inject
private MyService myService;
private static final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
#BeforeClass
public static void beforeClass() {
helper.setUp();
}
#AfterClass
public static void afterClass() {
helper.tearDown();
}
If anyone has a better solution, I'll be glad to hear!

Resources