How to mock Files.copy method in JUnit - spring

I am using some of the methods of Files class like (delete, copy methods) to do upload and delete of file. Below is the code to perform these operations.
public String uploadFile(MultipartFile file) {
try {
String fileName = file.getOriginalFilename()
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = Paths.get("uploadPath" + File.separator + StringUtils.cleanPath(fileName));
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Not able to upload", ex);
}
}
But for this source code I am not able to write JUnit tests because not able to mock Files class. For mocking final classes we can use PowerMock which supports to mock static and final methods. But here if I do using PowerMock still it is not mocking. I am using Spring Framework 5.2.1.RELEASE , Is there any change in JUnit with this version to mock final classes or methods? Or can any one help me on writing the unit tests for this code (versions I am using Spring Framework 5.2.1 and JUnit4.12).

Mocking static and final classes is indeed possible only with tools like PowerMock or PowerMockito, and its not related to JUnit or Spring frameworks.
I think you should not Mock Files.copy operation.
Instead consider the following strategy:
Define an interface for working with files, a kind of DAO but for file system:
public interface FileSystemDAO {
void copy(InputStream is, Path target, StandardCopyOption ... options);
}
public class FileSystemDAOImpl implements FileSystemDAO {
void copy(InputStream is, Path target, StandatadCopyOption ... options) {
Files.copy(...)
}
}
Now use dependency injection in all the places that work with files (if you're using spring as you've said - define FileSystemDAOImpl as a bean).
class MySampleUploadService {
private final FileSystemDAO fileSystemDao;
public MySampleUploadService(FileSystemDAO dao) {
this.fileSystemDao = dao;
}
public String uploadFile(MultipartFile file) {
try {
String fileName = file.getOriginalFilename()
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = Paths.get("uploadPath" + File.separator +
StringUtils.cleanPath(fileName));
fileSystemDao.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Not able to upload", ex);
}
}
}
Now with this approach you can easily test the Upload service by mocking the FileSystemDao interface.

Related

How can I get an application context inside a method of a Spring RestController annotated class

I have a simple app using the #SpringBootAnnotation with a single call on the main method:
SpringApplication.run(App.class, args);
On App.java I am also defining a couple of #BeanS, which give the instance of drivers to access external services:
#Bean
public APEWebservice ape() {
return new APEWebservice(apeWebAddress + ":" + apePort);
}
Then, on the method of one of my #RestControllerS I want to make an access to these beans, so that I can make further calls to these other services, something along these lines:
#PostMapping(path="/talk", consumes = "application/json")
#ResponseStatus(HttpStatus.ACCEPTED)
public Talk talk(#RequestBody InputTalk body) throws ConfigurationException {
ApplicationContext context = new AnnotationConfigApplicationContext(App.class);
APEWebservice ape = context.getBean("ape", APEWebservice.class);
String DRSString = ape.getSoloOutput(input, OutputType.DRSXML);
((ConfigurableApplicationContext)context).close();
try {
Commanded transformed = Preprocessor.transform(body.getContent(), DRSString);
return new Talk(counter.incrementAndGet(), transformed.execute());
}
catch (WrongCommandException e) {
return new Talk(counter.incrementAndGet(), e.getError());
}
}
This looks very ugly, and I am certain I am completely missing the point of Spring and dependency injection. Is there a way to access the context without having to initialize it for every call to the API?
I am using SpringBoot 2.2.1
What about injecting?
#Autowired
private final APEWebservice service;
//code
#PostMapping(path="/talk", consumes = "application/json")
#ResponseStatus(HttpStatus.ACCEPTED)
public Talk talk(#RequestBody InputTalk body) throws ConfigurationException {
String DRSString = service.getSoloOutput(input, OutputType.DRSXML);

JAXBElement: providing codec (/converter?) for class java.lang.Class

I have been evaluating to adopt spring-data-mongodb for a project. In summary, my aim is:
Using existing XML schema files to generate Java classes.
This is achieved using JAXB xjc
The root class is TSDProductDataType and is further modeled as below:
The thing to note here is that ExtensionType contains protected List<Object> any; allowing it to store Objects of any class. In my case, it is amongst the classes named TSDModule_Name_HereModuleType and can be browsed here
Use spring-data-mongodb as persistence store
This is achieved using a simple ProductDataRepository
#RepositoryRestResource(collectionResourceRel = "product", path = "product")
public interface ProductDataRepository extends MongoRepository<TSDProductDataType, String> {
TSDProductDataType queryByGtin(#Param("gtin") String gtin);
}
The unmarshalled TSDProductDataType, however, contains JAXBElement which spring-data-mongodb doesn't seem to handle by itself and throws a CodecConfigurationException org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.lang.Class.
Here is the faulty statement:
TSDProductDataType tsdProductDataType = jaxbElement.getValue();
repository.save(tsdProductDataType);
I tried playing around with Converters for spring-data-mongodb as explained here, however, it seems I am missing something since the exception is about "Codecs" and not "Converters".
Any help is appreciated.
EDIT:
Adding converters for JAXBElement
Note: Works with version 1.5.6.RELEASE of org.springframework.boot::spring-boot-starter-parent. With version 2.0.0.M3, hell breaks loose
It seems that I missed something while trying to add converter earlier. So, I added it like below for testing:
#Component
#ReadingConverter
public class JAXBElementReadConverter implements Converter<DBObject, JAXBElement> {
//#Autowired
//MongoConverter converter;
#Override
public JAXBElement convert(DBObject dbObject) {
Class declaredType, scope;
QName name = qNameFromString((String)dbObject.get("name"));
Object rawValue = dbObject.get("value");
try {
declaredType = Class.forName((String)dbObject.get("declaredType"));
} catch (ClassNotFoundException e) {
if (rawValue.getClass().isArray()) declaredType = List.class;
else declaredType = LinkedHashMap.class;
}
try {
scope = Class.forName((String) dbObject.get("scope"));
} catch (ClassNotFoundException e) {
scope = JAXBElement.GlobalScope.class;
}
//Object value = rawValue instanceof DBObject ? converter.read(declaredType, (DBObject) rawValue) : rawValue;
Object value = "TODO";
return new JAXBElement(name, declaredType, scope, value);
}
QName qNameFromString(String s) {
String[] parts = s.split("[{}]");
if (parts.length > 2) return new QName(parts[1], parts[2], parts[0]);
if (parts.length == 1) return new QName(parts[0]);
return new QName("undef");
}
}
#Component
#WritingConverter
public class JAXBElementWriteConverter implements Converter<JAXBElement, DBObject> {
//#Autowired
//MongoConverter converter;
#Override
public DBObject convert(JAXBElement jaxbElement) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", qNameToString(jaxbElement.getName()));
dbObject.put("declaredType", jaxbElement.getDeclaredType().getName());
dbObject.put("scope", jaxbElement.getScope().getCanonicalName());
//dbObject.put("value", converter.convertToMongoType(jaxbElement.getValue()));
dbObject.put("value", "TODO");
dbObject.put("_class", JAXBElement.class.getName());
return dbObject;
}
public String qNameToString(QName name) {
if (name.getNamespaceURI() == XMLConstants.NULL_NS_URI) return name.getLocalPart();
return name.getPrefix() + '{' + name.getNamespaceURI() + '}' + name.getLocalPart();
}
}
#SpringBootApplication
public class TsdApplication {
public static void main(String[] args) {
SpringApplication.run(TsdApplication.class, args);
}
#Bean
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(
new JAXBElementReadConverter(),
new JAXBElementWriteConverter()
));
}
}
So far so good. However, how do I instantiate MongoConverter converter;?
MongoConverter is an interface so I guess I need an instantiable class adhering to this interface. Any suggestions?
I understand the desire for convenience in being able to just map an existing domain object to the database layer with no boilerplate, but even if you weren't having the JAXB class structure issue, I would still be recommending away from using it verbatim. Unless this is a simple one-off project, you almost definitely will hit a point where your domain models will need to change but your persisted data need to remain in an existing state. If you are just straight persisting the data, you have no mechanism to convert between a newer domain schema and an older persisted data scheme. Versioning of the persisted data scheme would be wise too.
The link you posted for writing the customer converters is one way to achieve this and fits in nicely with the Spring ecosystem. That method should also solve the issue you are experiencing (about the underlying messy JAXB data structure not converting cleanly).
Are you unable to get that method working? Ensure you are loading them into the Spring context with #Component plus auto-class scanning or manually via some Configuration class.
EDIT to address your EDIT:
Add the following to each of your converters:
private final MongoConverter converter;
public JAXBElement____Converter(MongoConverter converter) {
this.converter = converter;
}
Try changing your bean definition to:
#Bean
public CustomConversions customConversions(#Lazy MongoConverter converter) {
return new CustomConversions(Arrays.asList(
new JAXBElementReadConverter(converter),
new JAXBElementWriteConverter(converter)
));
}

java.io.FileNotFoundException:/BOOT-inf/classes!/templates/

i am useing springboot create a project ,it's no error run idea,but, run app.jar file ,it's run Exception like this
java.io.FileNotFoundException: class path resource [templates/] cannot be resol
ed to absolute file path because it does not reside in the file system: jar:fil
:/E:/projects/help/target/zhx-help-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/templa
es/
at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:21
)
at org.springframework.core.io.AbstractFileResolvingResource.getFile(Ab
tractFileResolvingResource.java:52)
at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.get
emplateLoaderForPath(FreeMarkerConfigurationFactory.java:338)
at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.cre
teConfiguration(FreeMarkerConfigurationFactory.java:290)
at org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer
afterPropertiesSet(FreeMarkerConfigurer.java:116)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getO
ject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegist
y.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetB
an(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBea
(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory
springboot version :1.5.2
useing spring-data-jpa
I see you use freemarker. In spring boot you cannot use the normal File approach to get your templates because they are not accessible when you run an executable JAR (File cannot be loaded as a resource when inside the JAR)
Use the following approach to load your templates folder:
cfg.setTemplateLoader(new ClassTemplateLoader(getClass().getClassLoader(), "templates"));
Full example:
#Configuration
public class FreemarkerConfiguration {
#Bean
public freemarker.template.Configuration freemarkerConfig() throws IOException {
freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_23);
cfg.setTemplateLoader(new ClassTemplateLoader(getClass().getClassLoader(), "templates"));
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
return cfg;
}
}
If you are using jersey with spring boot, Jersey doesn't work well with Spring Boot. The reason for this error is, jersey is not able to auto discover all the rest resources. To fix this, register all the resources explicitly, registering package doesn't work. Hope this issue will be fixed in future versions of jersey.
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(Resource1.class);
register(Resource2.class);
register(Resource3.class);
}
}
FreeMarkerConfigurationFactory.class
protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) {
if(this.isPreferFileSystemAccess()) {
try {
Resource ex = this.getResourceLoader().getResource(templateLoaderPath);
File file = ex.getFile();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Template loader path [" + ex + "] resolved to file path [" + file.getAbsolutePath() + "]");
}
return new FileTemplateLoader(file);
} catch (IOException var4) {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Cannot resolve template loader path [" + templateLoaderPath + "] to [java.io.File]: using SpringTemplateLoader as fallback", var4);
}
return new SpringTemplateLoader(this.getResourceLoader(), templateLoaderPath);
}
} else {
this.logger.debug("File system access not preferred: using SpringTemplateLoader");
return new SpringTemplateLoader(this.getResourceLoader(), templateLoaderPath);
}
}
so make log lever info
<logger name="org.springframework.web" level="INFO"/>

How to override your Propertyfile for jUnit (maven)

I have a Propertyfile config.properties in which I store a Path which a class loads to read Files.
The properties are loaded like this:
public class PropertyConfig {
private static final Properties properties = new Properties();
static {
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public static String getSetting(String key) {
return properties.getProperty(key);
}
}
and the call in the relevant class is like this:
private static File savedGamesFolder = new File(PropertyConfig.getSetting("folder_for_saved_games"));
For testing purposes I want to be able to change the path to a test directory, or change the whole Property-file in a jUnit-TestCase. How can achieve this?
I'm using Maven if that helps.
Assuming you have your config.properties in
src/main/resources/config.properties
Note: you should nevertheless have your properties files somewhere in src/main/resources
Place your test configuration in
src/main/test/config.properties
That's it. No need to change your code.

How to copy Spring's component-scan

I want to search for some annotations in a Spring based web application, like #Entity. Therefore I need the same functionality like Spring involves when the server starts up and it looks for all classes that are annotated with #Component. In my case I don't create singleton's, it's just important for me to collect all those classes annotated with #Entity.
Is there any possibility to use existing Spring tools for that? I want to search exactly in the same namespace as Spring does for the #Component annotations.
Sure, look at parse() method in org.springframework.context.annotation.ComponentScanBeanDefinitionParser. This method is called when Spring encounters <context:component-scan/> in the XML configuration. Probably You can strip it a bit to better suit your needs, but it should serve as a comprehensive example.
The class You should be particularly interested in is org.springframework.context.annotation.ClassPathBeanDefinitionScanner. From JavaDoc:
Candidate classes are detected through configurable type filters. The default filters include classes that are annotated with Spring's #Component, #Repository, #Service, or #Controller stereotype.
BTW if you need less general solution, maybe your persistence provider has some API to fetch all entity classes?
Spring's built-in classpath scanning infrastructure (ClassPathBeanDefinitionScanner/ ComponentScanBeanDefinitionParser) is geared up for registering classes as BeanDefinitions within an Spring appcontext.
If you're just looking to obtain a list of classes annotated with a given annotation (rather than actually register them in Spring as bean definitions) take a look at the Google Reflections library.
Reflections allows you to scan your classpath using various filters, including an annotation filter.
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends SomeClassOrInterface>> subTypes = reflections.getSubTypesOf(SomeClassOrInterface.class);
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(SomeAnnotation.class);
Spring based solution
Use spring AnnotationTypeFilter and pass Entity.class as annotationType
using ResourcePatternResolver load all resouces(.class) under given pacakage
Use SimpleMetadataReaderFactory to get MetadataReader
for each resource you can call match on AnnotationTypeFilter using MetadataReader
metadataReader.getAnnotationMetadata().getClassName() will provide FQN of class
usage
AnnotatedClassFinder entityScanner = new AnnotatedClassFinder(Entity.class);
entityScanner.setPackages(Arrays.asList("org.myapp.domain"));
Collection<Class<?>> entities = entityScanner.findMarkedClassOfType();
public class AnnotatedClassFinder {
private static final String CLASS_RESOURCE_PATTERN = "**/*.class";
private List<String> packages;
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
private final ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils
.getResourcePatternResolver(resourceLoader);
private final MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
private final TypeFilter annotationFilter;
public AnnotatedClassFinder(final Class<? extends Annotation> annotationToScanFor) {
annotationFilter = new AnnotationTypeFilter(annotationToScanFor);
}
public Set<Class<?>> findMarkedClassOfType() {
if (packages == null) {
return new HashSet<Class<?>>();
}
final Set<Class<?>> annotatedClasses = new HashSet<Class<?>>();
try {
for (final String p : packages) {
final String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(p)) + "/"
+ CLASS_RESOURCE_PATTERN;
final Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (final Resource resource : resources) {
if (resource.isReadable()) {
final MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (annotationFilter.match(metadataReader, metadataReaderFactory)) {
annotatedClasses.add(Class.forName(metadataReader.getAnnotationMetadata().getClassName()));
}
}
}
}
return annotatedClasses;
} catch (final IOException ex) {
throw new RuntimeException("I/O failure during classpath scanning", ex);
} catch (final ClassNotFoundException ex) {
throw new RuntimeException("Class loading failure during classpath scanning", ex);
}
}
public void setPackages(final List<String> packages) {
this.packages = packages;
}
}

Resources