Setting Up a Test Context7:53 with Chris Ramacciotti
Wherever possible, we want to leave Spring out of our tests and focus completely on the "unit" under test. However, sometimes it's necessary to access Spring components, such as services, repositories, or other beans. For this reason, we can setup a minimal test
ApplicationContext to save us the overhead of starting an entire Spring
ApplicationContext. This video demonstrates one approach for setting up a test context.
Sync Your Code to the Start of this Video
git checkout -f v7
Using Git with this Workshop
See the README on Github for information on how to use Git with this repo to follow along with this workshop.
For testing the weather service, 0:00 we're going to need to dive a bit deeper into the Spring Test Framework. 0:02 Currently, our application's WeatherServiceImpl, 0:05 let me open that WeatherServiceImpl. 0:08 Currently, this class depends on properties that are loaded 0:11 from a properties file, you see that with the PropertySource annotation. 0:14 As well as an Autowired bean in the Impl's superclass, 0:18 which you see here is the RestApiService and 0:22 the Autowired bean that it depends on is this RestTemplate bean. 0:25 Now, you recall from earlier in the workshop, I mentioned that RestTemplate is 0:31 a way that we can use the Spring Framework to call upon external APIs. 0:35 The problem with these Autowired fields, as well as anything that receives its 0:41 value from a value annotation that comes from a properties file, 0:46 is that since we aren't loading the entire application context right now, 0:50 these things will not be injected in our tests. 0:54 No worries though, the Spring Test Framework offers us an option. 1:00 We can load a test context by providing a test specific configuration class. 1:03 Let's add our weather service test and get started. 1:09 So I'm back in the test source folder and 1:12 I'm going to add a class for weather service test. 1:17 WeatherServiceTest. 1:21 For starters, we'll need to use Spring's JUnit runner so 1:25 we'll annotate the class with a RunWith annotation and 1:29 specify the SpringJunit4ClassRunner as the JUnit runner to be used for these tests. 1:33 And next, in order to trigger the loading of a test context, 1:41 we'll annotate this class with a ContextConfiguration annotation. 1:44 Now without specifying a class or to location as an annotation element 1:51 on this ContextConfiguration annotation, Spring will look for 1:54 classes annotated with the configuration annotation in the current class. 1:58 So let's add a static class within WeatherServiceTest. 2:03 So I will say Configuration and 2:07 call it public static class TestConfig. 2:11 Now to get those properties from api.properties, 2:18 I'll annotate this class with the same type of PropertySource annotation 2:21 that we saw in our WeatherServiceImpl class. 2:26 So PropertySource, and I'll say api.properties here, 2:29 and then I will add as a field an Autowired environment variable. 2:34 So, Autowired Environment, I'll call it env. 2:40 And remember that Autowired RestTemplate? 2:47 Let's make that available as a bean here. 2:49 So we'll call it Bean public RestTemplate, 2:52 there it is, restTemplate. 2:57 There we go. 3:02 Now we don't have to duplicate code from our AppConfig 3:04 where the production bean is located. 3:07 Let's head there to reconfigure some things. 3:09 So I'll open AppConfig.java. 3:10 And here is that RestTemplate bean right here. 3:14 You'll find that as you gain experience writing tests, 3:18 you'll get better at writing code that's more testable and test friendly. 3:21 So it's the code in this RestTemplate bean here that we'd rather not duplicate. 3:24 What we can do is make a static method that loads a default RestTemplate and 3:30 this production bean can utilize it. 3:34 Also, our test can then utilize it as well. 3:35 So, I will make that right here. 3:38 We'll say public static RestTemplate, and again, call it restTemplate. 3:40 Actually, I'll call this one defaultRestTemplate. 3:48 There we go, now I'll move all the code from here 3:53 down into the static method just like this. 3:56 With one exception, this static method doesn't need that 4:01 RequestFactory here since all that really does, if you look at this, 4:05 is it sets some connection and read timeout. 4:10 We won't need that for our tests for now. 4:14 Also, this would cause a problem for us since the method, 4:17 this ClientHttpRequestFactory method, that is an instance method and 4:20 this one is a static method so we could not call upon this method from here. 4:26 Now we can go back up here to the production bean and 4:33 tweak it a little bit so that it's still intact. 4:35 So I'll create a RestTemplate here, I'll just call it rt, and 4:37 call that default static method here. 4:41 And then, I'll just set the RequestFactory here calling upon 4:44 that method below, and this is an instance method so 4:49 it can indeed call this instance method down here. 4:52 Okay, everything's intact and 4:55 we now have this public static method where we can call it from the test class. 4:58 And I do see that I'm missing my return value here, so 5:03 I'll return that RestTemplate so that it is available as the bean. 5:07 So now that we have things set up in a more reusable manner in our application 5:12 code, let's switch back to the test. 5:15 I'm gonna close this file. 5:17 Okay, so in our test, we can now call upon that static method from AppConfig. 5:18 So I'll just return AppConfig.defaultRestTemplate, 5:25 good enough. 5:30 And another bean that we'll need to make available is a WeatherService bean, 5:31 we'll have to do this because it is our application class that contains 5:36 the ComponentScan annotation. 5:40 If I go to Application.java, I see the ComponentScan annotation which will tell 5:42 Spring that when it boots the application, remember, the application not the test, 5:46 when Spring boots the application, it'll scan the current package and 5:51 any sub packages of whatever application is located here. 5:56 So it'll scan all these sub packages for 6:00 any Spring components that are annotated with service, repository, or 6:02 any other Spring components that may be present in these packages. 6:07 Now this includes the service, 6:13 the WeatherServiceImpl that we are going to be testing. 6:16 This is annotated with a service, so when the application boots, 6:21 this WeatherService will be picked up as an auto-wireable bean and 6:25 we need that to be the case in our test as well. 6:30 So we'll need to construct and return the implementation ourselves. 6:34 Let's go back into our test and add that as a bean. 6:37 So we'll say Bean. 6:40 Public WeatherService weatherService. 6:44 And in here let's construct this. 6:51 So WeatherService, I'll just call it service = new, 6:53 I'll have to create the implementation myself and 7:00 pass in all those values that we can now grab from the env field. 7:04 Okay, so let me do that on separate lines here. 7:10 So I'll say env.getProperty, and the first one is weather.api.name. 7:13 And I'll just copy that and paste it a few times. 7:20 The next one we'll say is weather.api.key and 7:23 the next one is weather.api.host. 7:32 Cool, I've created my service implementation, 7:39 and now let's return it as the bean. 7:41 Cool, and at this point we're ready to write our test for 7:46 the weather service which is exactly what we're going to do in the next video. 7:48
You need to sign up for Treehouse in order to download course files.Sign up