001/*
002 * Copyright 2012-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.test.json;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.Reader;
022
023import com.fasterxml.jackson.databind.JavaType;
024import com.fasterxml.jackson.databind.ObjectMapper;
025import com.fasterxml.jackson.databind.ObjectReader;
026import com.fasterxml.jackson.databind.ObjectWriter;
027
028import org.springframework.beans.factory.ObjectFactory;
029import org.springframework.core.ResolvableType;
030import org.springframework.util.Assert;
031
032/**
033 * AssertJ based JSON tester backed by Jackson. Usually instantiated via
034 * {@link #initFields(Object, ObjectMapper)}, for example: <pre class="code">
035 * public class ExampleObjectJsonTests {
036 *
037 *     private JacksonTester&lt;ExampleObject&gt; json;
038 *
039 *     &#064;Before
040 *     public void setup() {
041 *         ObjectMapper objectMapper = new ObjectMapper();
042 *         JacksonTester.initFields(this, objectMapper);
043 *     }
044 *
045 *     &#064;Test
046 *     public void testWriteJson() throws IOException {
047 *         ExampleObject object = //...
048 *         assertThat(json.write(object)).isEqualToJson("expected.json");
049 *     }
050 *
051 * }
052 * </pre>
053 *
054 * See {@link AbstractJsonMarshalTester} for more details.
055 *
056 * @param <T> the type under test
057 * @author Phillip Webb
058 * @author Madhura Bhave
059 * @since 1.4.0
060 */
061public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
062
063        private final ObjectMapper objectMapper;
064
065        private Class<?> view;
066
067        /**
068         * Create a new {@link JacksonTester} instance.
069         * @param objectMapper the Jackson object mapper
070         */
071        protected JacksonTester(ObjectMapper objectMapper) {
072                Assert.notNull(objectMapper, "ObjectMapper must not be null");
073                this.objectMapper = objectMapper;
074        }
075
076        /**
077         * Create a new {@link JacksonTester} instance.
078         * @param resourceLoadClass the source class used to load resources
079         * @param type the type under test
080         * @param objectMapper the Jackson object mapper
081         */
082        public JacksonTester(Class<?> resourceLoadClass, ResolvableType type,
083                        ObjectMapper objectMapper) {
084                this(resourceLoadClass, type, objectMapper, null);
085        }
086
087        public JacksonTester(Class<?> resourceLoadClass, ResolvableType type,
088                        ObjectMapper objectMapper, Class<?> view) {
089                super(resourceLoadClass, type);
090                Assert.notNull(objectMapper, "ObjectMapper must not be null");
091                this.objectMapper = objectMapper;
092                this.view = view;
093        }
094
095        @Override
096        protected T readObject(InputStream inputStream, ResolvableType type)
097                        throws IOException {
098                return getObjectReader(type).readValue(inputStream);
099        }
100
101        @Override
102        protected T readObject(Reader reader, ResolvableType type) throws IOException {
103                return getObjectReader(type).readValue(reader);
104        }
105
106        private ObjectReader getObjectReader(ResolvableType type) {
107                ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
108                if (this.view != null) {
109                        return objectReader.withView(this.view);
110                }
111                return objectReader;
112        }
113
114        @Override
115        protected String writeObject(T value, ResolvableType type) throws IOException {
116                return getObjectWriter(type).writeValueAsString(value);
117        }
118
119        private ObjectWriter getObjectWriter(ResolvableType type) {
120                ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
121                if (this.view != null) {
122                        return objectWriter.withView(this.view);
123                }
124                return objectWriter;
125        }
126
127        private JavaType getType(ResolvableType type) {
128                return this.objectMapper.constructType(type.getType());
129        }
130
131        /**
132         * Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
133         * class-level documentation} for example usage.
134         * @param testInstance the test instance
135         * @param objectMapper the object mapper
136         * @see #initFields(Object, ObjectMapper)
137         */
138        public static void initFields(Object testInstance, ObjectMapper objectMapper) {
139                new JacksonFieldInitializer().initFields(testInstance, objectMapper);
140        }
141
142        /**
143         * Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
144         * class-level documentation} for example usage.
145         * @param testInstance the test instance
146         * @param objectMapperFactory a factory to create the object mapper
147         * @see #initFields(Object, ObjectMapper)
148         */
149        public static void initFields(Object testInstance,
150                        ObjectFactory<ObjectMapper> objectMapperFactory) {
151                new JacksonFieldInitializer().initFields(testInstance, objectMapperFactory);
152        }
153
154        /**
155         * Returns a new instance of {@link JacksonTester} with the view that should be used
156         * for json serialization/deserialization.
157         * @param view the view class
158         * @return the new instance
159         */
160        public JacksonTester<T> forView(Class<?> view) {
161                return new JacksonTester<>(this.getResourceLoadClass(), this.getType(),
162                                this.objectMapper, view);
163        }
164
165        /**
166         * {@link FieldInitializer} for Jackson.
167         */
168        private static class JacksonFieldInitializer extends FieldInitializer<ObjectMapper> {
169
170                protected JacksonFieldInitializer() {
171                        super(JacksonTester.class);
172                }
173
174                @Override
175                protected AbstractJsonMarshalTester<Object> createTester(
176                                Class<?> resourceLoadClass, ResolvableType type,
177                                ObjectMapper marshaller) {
178                        return new JacksonTester<>(resourceLoadClass, type, marshaller);
179                }
180
181        }
182
183}