I have a web application project, and I'm trying to unit test a method that creates a file using a FreeMarker template. My method createFile() should take a MyFile type - that contains the File name to create and the rootMap FreeMarker needs and the template name - and create a file using the template I provide.
I am following the Freemarker manual to set a Template Loader. The problem is, I'm using the TemplateLoader setClassForTemplateLoading(Class, String) method to find the template path. This template loader uses the Class.getResource() to get the classpath.
But, since I'm using Maven, I have my java code in /src/main/java, my template in /src/main/webapp/templates/ and my test code in /src/test/java. Therefore, my Class.getResource("/") (root classpath) always returns <PATH_TO_PROJECT>/target/test-classes/.
Since I will be deploying a war, I cannot use the setDirectoryForTemplateLoading(File). Also, since I'm testing my app I don't have a ServletContext to use with setServletContextForTemplateLoading(Object, String).
How can I access my template folder from the test case?
Here's a simplified example of my test code (I use mockito to mock the behaviour of the MyFile class):
private MyFile myFile;
private FileGenerator fileGenerator;
#Before
public void setUp() {
myFile = new MyFile(...);
fileGenerator = new FileGenerator(myFile, ...);
}
#Test
public void shouldCreateFile() {
final MyFile mockedMyFile = spy(file);
final Map<String, Object> rootMap = new HashMap<String, Object>();
// populates rootMap with stuff needed for the Template
// mocking method return
when(mockedMyFile.getRootMap()).thenReturn(rootMap);
// replacing the MyFile implementation with my Mock
fileGenerator.setMyFile(mockedMyFile);
// calling the method I want to test
fileGenerator.createFile();
assertTrue(MyFile.getFile().exists());
}
And here is a simplification of the code I'm testing:
public void createFile() {
final Configuration cfg = new Configuration();
cfg.setClassForTemplateLoading(getClass(), "templates/");
try {
myFile.getFile().createNewFile();
final Template template = cfg.getTemplate("template.ftl");
final Writer writer = new FileWriter(myFile.getFile());
template.process(myFile.getRootMap(), writer);
writer.flush();
writer.close();
}
// exception handling
}
I applied Charles Forsythe's suggestion, it worked out fine.
I just added a templateLoader member to the FileGenerator class, with its own getter and setter.
Next, in my createFile method, I use the method setTemplateLoader(TemplateLoader) from the Configuration class, as such:
public void createFile() {
final Configuration cfg = new Configuration();
// Changed
cfg.setTemplateLoader(templateLoader);
// the rest
}
Finally, I just create a template loader for my test:
#Test
public void shouldCreateFile() {
final MyFile mockedMyFile = spy(file);
final Map<String, Object> rootMap = new HashMap<String, Object>();
TemplateLoader templateLoader = null;
try {
templateLoader = new FileTemplateLoader(new File("<PATH TO TEMPLATE FOLDER>"));
} catch (final IOException e) {
e.printStackTrace();
}
gerador.setTemplateLoader(templateLoader);
// the rest
}
And problem solved. In my production code, I use ClassTemplateLoader instead of FileTemplateLoader.
Related
In a multi-tenant Spring Boot application, I'm trying to load configuration objects. Ideally, I'd like to load certain properties file into a configuration object programmatically. I'm looking for a simple way to load the configuration by passing a properties file and the final class to map it to. The following is just an example of what I'm trying to achieve.
Directory structure of the configurations:
config/
- common.properties
all_tenants_config/
- foo_tenant/
- database.properties
- api.properties
- bar_tenant/
- database.properties
- api.properties
Configuration POJOs:
class DatabaseProperties {
#Value("${database.url}")
private String url;
}
class APIProperties {
#Value("${api.endPoint}")
private String endPoint;
}
Configuration Provider:
#Singleton
class ConfigurationProvider {
private Map<String, DatabaseProperties> DB_PROPERTIES = new HashMap<>();
private Map<String, APIProperties> API_PROPERTIES = new HashMap<>();
public ConfigurationProvider(#Value(${"tenantsConfigPath"}) String tenantsConfigPath) {
for (File tenant : Path.of(tenantsConfigPath).toFile().listFiles()) {
String tenantName = tenant.getName();
for (File configFile : tenant.listFiles()) {
String configName = configFile.getName();
if ("database.properties".equals(configName)) {
// This is what I'm looking for. An easy way to load the configuration by passing a properties file and the final class to map it to.
DB_PROPERTIES.put(tenant, SPRING_CONFIG_LOADER.load(configFile, DatabaseProperties.class));
} else if ("api.properties".equals(configName)) {
API_PROPERTIES.put(tenant, SPRING_CONFIG_LOADER.load(configFile, API.class));
}
}
}
}
public currentTenantDBProperties() {
return DB_PROPERTIES.get(CURRENT_TENANT_ID);
}
public currentTenantAPIProperties() {
return API_PROPERTIES.get(CURRENT_TENANT_ID);
}
}
In short, is there a way in Spring that allows to map a properties file to an object without using the default Spring's configuration annotations.
Well, in this case you do not need any Spring's feature.
Spring is a bean container, but in this place you just new an object by yourself and put it on your map cache.
Step 1: decode property file to Java Properties Class Object
Step 2: turn your properties object to your target object, just use some utils like objectmapper
FileReader reader = new FileReader("db.properties"); // replace file name with your variable
Properties p = new Properties();
p.load(reader);
ObjectMapper mapper = new ObjectMapper();
DatabaseProperties databaseProperties = mapper.convertValue(p,
DatabaseProperties.class);
I am writing Junit test case for the following class :
#Component
public class ExpandParam {
/* expand parameter with value "expand" */
#Value("${api.expand.value}")
private String expandParam;
public MultiValueMap<String, String> getExpandQueryParam(String[] expand) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
// Creating comma separated format string
StringBuilder builder = new StringBuilder();
for (String value : expand) {
if(!expand[expand.length-1].equals(value)) {
builder.append(value+", ");
}
else {
builder.append(value);
}
}
String expandText = builder.toString();
queryParams.add(expandParam, expandText);
return queryParams;
}
}
The test class is following :
public class ExpandParamTest {
#InjectMocks
#Spy
ExpandParam expandQueryParam;
// #Value("${api.expand.value}")
// private String expandParam;
private String[] expand = {"fees"};
#Before
public void setup() {
ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand");
}
#Test
public void testExpandParam() {
MultiValueMap<String, String> queryParams = expandQueryParam.getExpandQueryParam(expand);
try {
System.out.println(new ObjectMapper().writeValueAsString(queryParams));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In application. properties files I have set the values :
#expand param
api.expand.value: expand
I am new to this, can any one tell me where I am making the mistake:
Getting the following error:
java.lang.IllegalArgumentException: Either targetObject or targetClass for the field must be specified
at org.springframework.util.Assert.isTrue(Assert.java:121)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:178)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:107)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:91)
at com.aig.rs.products.orchestrator.api.utils.ExpandParamTest.setup(ExpandParamTest.java:29)
#Value is a spring annotation, it depends on the Spring Context to function. If you want #Value to read the value from your application properties then you need to convert your unit test into a #SpringBootTest. Take a look at this tutorial to understand a bit more about Spring Test.
You're also using ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand"); which will just set a value to this field, not read it from properties. This exception you're seeing is because expandQueryParam is null, these annotations #Spy and #InjectMocks are Mockito annotations and for them to initialize your object you need to enable mockito annotations, you can do this by adding #ExtendWith(MockitoExtension.class) on top of your class or using MockitoAnnotations.initMocks(this) in setUp method.
I don't think you need mockito to test this class, in my opinion going for a Spring Test would be a better option this way you can also test the reading of the property key.
My application expects to find a configuration file called MyPojo.json, loaded into MyPojo class by MyService class:
#Data // (Lombok's) getters and setters
public class MyPojo {
int foo = 42;
int bar = 1337;
}
It's not a problem if it doesn't exist: in that case, the application will create it with default values.
The path where to read/write MyPojo.json is stored in /src/main/resources/settings.properties:
the.path=cfg/MyPojo.json
which is passed to MyService through Spring's #PropertySource as follows:
#Configuration
#PropertySource("classpath:settings.properties")
public class MyService {
#Inject
Environment settings; // "src/main/resources/settings.properties"
#Bean
public MyPojo load() throws Exception {
MyPojo pojo = null;
// "cfg/MyPojo.json"
Path path = Paths.get(settings.getProperty("the.path"));
if (Files.exists(confFile)){
pojo = new ObjectMapper().readValue(path.toFile(), MyPojo.class);
} else { // JSON file is missing, I create it.
pojo = new MyPojo();
Files.createDirectory(path.getParent()); // create "cfg/"
new ObjectMapper().writeValue(path.toFile(), pojo); // create "cfg/MyPojo.json"
}
return pojo;
}
}
Since MyPojo's path is relative, when I run this from a Unit Test
#Test
public void testCanRunMockProcesses() {
try (AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(MyService.class)){
MyPojo pojo = ctx.getBean(MyPojo.class);
String foo = pojo.getFoo();
...
// do assertion
}
}
the cfg/MyPojo.json is created under the root of my project, which is definitely not what I want.
I would like MyPojo.json to be created under my target folder, eg. /build in Gradle projects, or /target in Maven projects.
To do that, I've created a secondary settings.properties under src/test/resources, containing
the.path=build/cfg/MyPojo.json
and tried to feed it to MyService in several ways, without success.
Even if called by the test case, MyService is always reading src/main/resources/settings.properties instead of src/test/resources/settings.properties.
With two log4j2.xml resources instead (src/main/resources/log4j2.xml and src/test/resources/log4j2-test.xml), it worked :/
Can I do the same with a property file injected by Spring with #PropertySource ?
You can use #TestPropertySource annotation for this.
Example:
For single property:
#TestPropertySource(properties = "property.name=value")
For property file
#TestPropertySource(
locations = "classpath:yourproperty.properties")
So, you provide path for MyPojo.json like
#TestPropertySource(properties = "path=build/cfg/MyPojo.json")
I've the following piece of code:
Map<String, String> fileContentsByName = new HashMap<String, String>();
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory))
{
for (Path path : directoryStream)
{
if (Files.isRegularFile(path))
{
fileContentsByName.put(path.getFileName().toString(), new String(Files.readAllBytes(path)));
}
}
}
catch (IOException e)
{
}
I am attempting to test this method. I'm using Powermock to get the mocked DirectoryStream<Path>. However, when the test encounters for-each in the code, it blows up with a NPE. How can I specify the Paths in the DirectoryStream?
I've thought about changing the source code to use iterator and mocking the DirectoryStream's iterator to provide the desired paths but I am wondering if there a better alternative?
Assuming that the code snippet you provided above is defined in a class like so:
public class DirectoryStreamReader {
public Map<String, String> read(Path directory) {
Map<String, String> fileContentsByName = new HashMap<String, String>();
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
for (Path path : directoryStream) {
if (Files.isRegularFile(path)) {
fileContentsByName.put(path.getFileName().toString(), new String(Files.readAllBytes(path)));
}
}
} catch (IOException e) {
}
return fileContentsByName;
}
}
Then the following test will pass:
#RunWith(PowerMockRunner.class)
#PrepareForTest({DirectoryStreamReader.class})
public class DirectoryStreamTest {
#Rule
public TemporaryFolder folder= new TemporaryFolder();
#Test
public void canReadFilesUsingDirectoryStream() throws IOException {
PowerMockito.mockStatic(Files.class);
Path directory = Mockito.mock(Path.class);
DirectoryStream<Path> expected = Mockito.mock(DirectoryStream.class);
Mockito.when(Files.newDirectoryStream(Mockito.any(Path.class))).thenReturn(expected);
File fileOne = folder.newFile();
File fileTwo = folder.newFile();
Iterator<Path> directoryIterator = Lists.newArrayList(Paths.get(fileOne.toURI()),
Paths.get(fileTwo.toURI())).iterator();
Mockito.when(expected.iterator()).thenReturn(directoryIterator);
Mockito.when(Files.isRegularFile(Mockito.any(Path.class))).thenReturn(true);
Mockito.when(Files.readAllBytes(Mockito.any(Path.class))).thenReturn("fileOneContents".getBytes()).thenReturn("fileTwoContents".getBytes());
Map<String, String> fileContentsByName = new DirectoryStreamReader().read(directory);
Assert.assertEquals(2, fileContentsByName.size());
Assert.assertTrue(fileContentsByName.containsKey(fileOne.getName()));
Assert.assertEquals("fileOneContents", fileContentsByName.get(fileOne.getName()));
Assert.assertTrue(fileContentsByName.containsKey(fileTwo.getName()));
Assert.assertEquals("fileTwoContents", fileContentsByName.get(fileTwo.getName()));
}
}
The key points here are:
Uses JUnit's TemporaryFolder rule to create and discard some files for use by the test
Uses PowerMockito to mock all interactions with java.nio.file.Files, this is a final class and the methods being mocked are static hence the need for PowerMockito
Follows the PowerMockito advice when mocking a system class, specifically:
Use #RunWith(PowerMockRunner.class) annotation at the class-level of the test case.
Use the #PrepareForTest({ClassThatCallsTheSystemClass.class}) annotation at the class-level of the test case.
Use mockStatic(SystemClass.class) to mock the system class
This test is verified with Junit 4.12, Mockito 2.7.19 and PowerMock 1.7.0
My idea is to create a file in a Test then eliminate it, but my code fails
My code:
#RunWith(RemoteTestRunner.class)
#Remote(runnerClass=SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:alfresco/application-context.xml")
public class FooTest {
private static final String ADMIN_USER_NAME = "admin";
#Autowired
#Qualifier("NodeService")
protected NodeService nodeService;
#Autowired
private FileFolderService fileFolderService;
#Autowired
protected Repository repositoryHelper;
#Test
public void testCreateTempFile() {
AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER_NAME);
NodeRef root = repositoryHelper.getSharedHome();
// create the node
// this line throw an exception
FileInfo fileInfo = fileFolderService.create(root,
"foo.txt", ContentModel.PROP_CONTENT);
...
}
}
It appears the following error:
org.alfresco.error.AlfrescoRuntimeException: 07170002 Transaction must
be active and synchronization is required: Thread[main,5,main] at
org.alfresco.util.transaction.TransactionSupportUtil.registerSynchronizations(TransactionSupportUtil.java:188)
How the transactions work? How can I activate it?
In order to work with temporary file alfresco provides one utility class
org.alfresco.util.TempFileProvider
It has all the API which allows you to create temporary files and directories.
If you have extensive usage of temporary files you can even consider using this utility which allows you to clear temporary files on regular interval.
Have you tried with the #Transactional annotation ?
#Test
#Transactional
public void testCreateTempFile() {
....
This should resolve your problem.
I found the solution:
RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper();
final String filename = "TestSite" + System.currentTimeMillis();
NodeRef noderef = transactionHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>() {
public NodeRef execute() throws Throwable {
final NodeRef parent = repositoryHelper.getCompanyHome();
FileInfo fileInfo = fileFolderService.create(parent, filename, ContentModel.PROP_CONTENT);
NodeRef node = fileInfo.getNodeRef();
ContentWriter writer = contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setEncoding("UTF-8");
writer.putContent(content);
return node;
}
});
In Junit is mandatory to use transaction to create files.