Close

Spring Boot - Unit Testing Web Application With Mock MVC

[Updated: Mar 22, 2020, Created: Mar 21, 2020]

To use Spring Mock MVC Test Framework, we need to use @AutoConfigureMockMvc.

In this case @SpringBootTest#webEnvironment should be assigned to WebEnvironment.MOCK (default). This loads a web ApplicationContext and provides a mock web environment. Embedded servers are not started when using this annotation. If a web environment is not available on the classpath, this mode transparently falls back to creating a regular non-web ApplicationContext.

Example

Example Spring Boot web application

@Controller
public class MyMvcController {
  @Autowired
  private HelloService helloService;

  @GetMapping(value = "/")
  public String sayHi(String name, Model model) {
      model.addAttribute("msg", helloService.getMessage(name));
      return "hello-page";
  }
}
@Service
public class HelloService {
  public String getMessage(String name) {
      return String.format("Hi there, %s.", name);
  }
}
@SpringBootApplication
public class WebAppMain {
  public static void main(String[] args) {
      SpringApplication.run(WebAppMain.class, args);
  }
}

The unit test

package com.logicbig.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyWebAppTest {
  @Autowired
  private MockMvc mockMvc;

  @Test
  public void testSayHi() throws Exception {
      this.mockMvc.perform(MockMvcRequestBuilders.get("/").param("name", "Joe"))
                  .andExpect(MockMvcResultMatchers.status().isOk())
                  .andExpect(MockMvcResultMatchers.model().attribute("msg", "Hi there, Joe."))
                  .andExpect(MockMvcResultMatchers.view().name("hello-page"))
                  .andDo(MockMvcResultHandlers.print());
  }
}
D:\boot-unit-testing-with-mock-mvc>mvn test -Dtest=MyWebAppTest.java
[INFO] Scanning for projects...
[INFO]
[INFO] --------< com.logicbig.example:boot-unit-testing-with-mock-mvc >--------
[INFO] Building boot-unit-testing-with-mock-mvc 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ boot-unit-testing-with-mock-mvc ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ boot-unit-testing-with-mock-mvc ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ boot-unit-testing-with-mock-mvc ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ boot-unit-testing-with-mock-mvc ---
[INFO] Changes detected - recompiling the module!
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ boot-unit-testing-with-mock-mvc ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
00:03:00.181 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.logicbig.example.MyWebAppTest]
00:03:00.192 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
00:03:00.218 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
00:03:00.275 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.logicbig.example.MyWebAppTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
00:03:00.293 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.logicbig.example.MyWebAppTest], using SpringBootContextLoader
00:03:00.298 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.logicbig.example.MyWebAppTest]: class path resource [com/logicbig/example/MyWebAppTest-context.xml] does not exist
00:03:00.299 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.logicbig.example.MyWebAppTest]: class path resource [com/logicbig/example/MyWebAppTestContext.groovy] does not exist
00:03:00.299 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.logicbig.example.MyWebAppTest]: no resource found for suffixes {-context.xml, Context.groovy}.
00:03:00.300 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.logicbig.example.MyWebAppTest]: MyWebAppTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
00:03:00.364 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.logicbig.example.MyWebAppTest]
00:03:00.456 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.logicbig.example.WebAppMain for test class com.logicbig.example.MyWebAppTest
00:03:00.586 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.logicbig.example.MyWebAppTest]: using defaults.
00:03:00.587 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
00:03:00.601 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
00:03:00.602 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
00:03:00.603 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@44be0077, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@2205a05d, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@72ef8d15, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@6aa8e115, org.springframework.test.context.support.DirtiesContextTestExecutionListener@5e21e98f, org.springframework.test.context.event.EventPublishingTestExecutionListener@51a9ad5e, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@5f20155b, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@72ade7e3, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@239105a8, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@3fce8fd9, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@609bcfb6]
00:03:00.610 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.logicbig.example.MyWebAppTest]
00:03:00.611 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.logicbig.example.MyWebAppTest]
00:03:00.646 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.logicbig.example.MyWebAppTest]
00:03:00.646 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
00:03:00.647 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
00:03:00.648 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.logicbig.example.MyWebAppTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
00:03:00.650 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.logicbig.example.MyWebAppTest], using SpringBootContextLoader
00:03:00.652 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.logicbig.example.MyWebAppTest]: class path resource [com/logicbig/example/MyWebAppTest-context.xml] does not exist
00:03:00.652 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.logicbig.example.MyWebAppTest]: class path resource [com/logicbig/example/MyWebAppTestContext.groovy] does not exist
00:03:00.653 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.logicbig.example.MyWebAppTest]: no resource found for suffixes {-context.xml, Context.groovy}.
00:03:00.653 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.logicbig.example.MyWebAppTest]: MyWebAppTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
00:03:00.660 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.logicbig.example.MyWebAppTest]
00:03:00.661 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.logicbig.example.WebAppMain for test class com.logicbig.example.MyWebAppTest
00:03:00.664 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.logicbig.example.MyWebAppTest]: using defaults.
00:03:00.665 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
00:03:00.666 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
00:03:00.667 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
00:03:00.667 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@2a225dd7, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@61eaec38, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@125290e5, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@6fa34d52, org.springframework.test.context.support.DirtiesContextTestExecutionListener@57576994, org.springframework.test.context.event.EventPublishingTestExecutionListener@616ac46a, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@67b9b51a, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@1205bd62, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@7ef27d7f, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@490caf5f, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@6337c201]
00:03:00.668 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.logicbig.example.MyWebAppTest]
00:03:00.669 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.logicbig.example.MyWebAppTest]
00:03:00.760 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.logicbig.example.MyWebAppTest]
00:03:00.761 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.logicbig.example.MyWebAppTest]
[INFO] Running com.logicbig.example.MyWebAppTest
00:03:00.765 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.logicbig.example.MyWebAppTest]
00:03:00.765 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.logicbig.example.MyWebAppTest]
00:03:00.766 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.logicbig.example.MyWebAppTest]
00:03:00.766 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.logicbig.example.MyWebAppTest]
00:03:00.772 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@30e868be testClass = MyWebAppTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@66c92293 testClass = MyWebAppTest, locations = '{}', classes = '{class com.logicbig.example.WebAppMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@332796d3 key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5aebe890, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@75db5df9, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@38102d01, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3098cf3b], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
00:03:00.781 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.logicbig.example.MyWebAppTest]
00:03:00.782 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.logicbig.example.MyWebAppTest]
00:03:00.811 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=-1}

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE)

2020-03-22 00:03:01.294 INFO 7332 --- [ main] com.logicbig.example.MyWebAppTest : No active profile set, falling back to default profiles: default
2020-03-22 00:03:03.212 INFO 7332 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-22 00:03:03.701 INFO 7332 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2020-03-22 00:03:03.701 INFO 7332 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2020-03-22 00:03:03.723 INFO 7332 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 21 ms
2020-03-22 00:03:03.751 INFO 7332 --- [ main] com.logicbig.example.MyWebAppTest : Started MyWebAppTest in 2.93 seconds (JVM running for 4.367)

MockHttpServletRequest:
HTTP Method = GET
Request URI = /
Parameters = {name=[Joe]}
Headers = []
Body = null
Session Attrs = {}

Handler:
Type = com.logicbig.example.MyMvcController
Method = com.logicbig.example.MyMvcController#sayHi(String, Model)

Async:
Async started = false
Async result = null

Resolved Exception:
Type = null

ModelAndView:
View name = hello-page
View = null
Attribute = msg
value = Hi there, Joe.

FlashMap:
Attributes = null

MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Language:"en"]
Content type = null
Body =
Forwarded URL = hello-page
Redirected URL = null
Cookies = []
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.566 s - in com.logicbig.example.MyWebAppTest
2020-03-22 00:03:04.362 INFO 7332 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.130 s
[INFO] Finished at: 2020-03-22T00:03:04-05:00
[INFO] ------------------------------------------------------------------------

Example Project

Dependencies and Technologies Used:

  • Spring Boot 2.2.5.RELEASE
  • spring-boot-starter-web : Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container.
  • spring-boot-starter-test : Starter for testing Spring Boot applications with libraries including JUnit, Hamcrest and Mockito.
  • JDK 1.8
  • Maven 3.5.4

boot-unit-testing-with-mock-mvc Select All Download
  • boot-unit-testing-with-mock-mvc
    • src
      • main
        • java
          • com
            • logicbig
              • example
      • test
        • java
          • com
            • logicbig
              • example
                • MyWebAppTest.java

    See Also