Introduction
JUnit is an open-source unit testing framework for Java that is used to write and run repeatable automated tests. Unit testing is one of the best test methods for regression testing.
Mockito is an open-source testing framework for Java that allows the creation of test double objects in automated unit tests for the purpose of test-driven development or behavior-driven development.
Authcontrollertest.java
@SpringBootTest annotation can be specified on a test class that runs Spring Boot based tests. It provides the following features over and above the regular Spring TestContext Framework:
- Uses
SpringBootContextLoader
as the defaultContextLoader
when no specific@ContextConfiguration(loader=...)
is defined. - Automatically searches for a
@SpringBootConfiguration
when nested@Configuration
is not used, and no explicitclasses
are specified. - Allows custom
Environment
properties to be defined using theproperties attribute
. - Allows application arguments to be defined using the
args attribute
. - Provides support for different
webEnvironment
modes, including the ability to start a fully running web server listening on adefined
orrandom
port. - Registers a
TestRestTemplate
and/orWebTestClient
bean for use in web tests that are using a fully running web server.
@AutoConfigureMockMvc annotation can be applied to a test class to enable and configure auto-configuration of MockMvc which provides the server-side Spring MVC test support.
Note: You can also use @WebMvcTest annotation that focuses on Spring MVC components. This annotation will disable full auto-configuration and only apply configuration relevant to MVC tests. If you are looking to load your full application configuration and use MockMVC, you should consider @SpringBootTest combining with @AutoConfigureMockMvc rather than this annotation.
@MockBean annotation can be used to add mocks to a Spring ApplicationContext. It can be used as a class-level annotation or on fields in either @Configuration classes or test classes that are @RunWith the SpringRunner.
package com.javachinna.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
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.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.javachinna.config.MockUserUtils;
import com.javachinna.config.WithMockCustomUser;
import com.javachinna.dto.LocalUser;
import com.javachinna.dto.LoginRequest;
import com.javachinna.dto.SignUpRequest;
import com.javachinna.dto.SocialProvider;
import com.javachinna.exception.UserAlreadyExistAuthenticationException;
import com.javachinna.model.User;
import com.javachinna.service.UserService;
import dev.samstevens.totp.code.CodeVerifier;;
@SpringBootTest
@AutoConfigureMockMvc
class AuthControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@MockBean
private CodeVerifier verifier;
@MockBean
private AuthenticationManager authenticationManager;
private static User user = MockUserUtils.getMockUser("JavaChinna");
private static ObjectMapper mapper = new ObjectMapper();
@Test
public void testAuthenticateUser() throws Exception {
LocalUser localUser = LocalUser.create(user, null, null, null);
LoginRequest loginRequest = new LoginRequest(user.getEmail(), user.getPassword());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(localUser, null);
Mockito.when(authenticationManager.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class))).thenReturn(authentication);
String json = mapper.writeValueAsString(loginRequest);
mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty());
// Test when user 2fa is enabled
user.setUsing2FA(true);
mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("false")).andExpect(jsonPath("$.user").doesNotExist());
}
@Test
public void testRegisterUser() throws Exception {
SignUpRequest signUpRequest = new SignUpRequest("1234", "JavaChinna", user.getEmail(), user.getPassword(), user.getPassword(), SocialProvider.FACEBOOK);
Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenReturn(user);
String json = mapper.writeValueAsString(signUpRequest);
mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.success").value("true")).andExpect(jsonPath("$.message").value("User registered successfully"));
// Test when user provided email already exists in the database
Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenThrow(new UserAlreadyExistAuthenticationException("exists"));
json = mapper.writeValueAsString(signUpRequest);
mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Email Address already in use!"));
}
@Test
@WithMockCustomUser
public void testVerifyCodeWhenCodeIsNotValid() throws Exception {
Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(false);
String json = mapper.writeValueAsString("443322");
mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Invalid Code!"));
}
@Test
@WithMockCustomUser
public void testVerifyCodeWhenCodeIsValid() throws Exception {
Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(true);
String json = mapper.writeValueAsString("443322");
mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty())
.andExpect(jsonPath("$.user").exists());
}
}
How can i use a unit test to test my api’s authentication and authorization?
A colleague wrote an HTTP API. He implemented the security using a DelegatingHandler that implements basic HTTP authorization.
He added a route config to apply the BasicAuthHandler to the API route in a global config:
config.Routes.MapHttpRoute(
name: "Api",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: BasicAuthHandler
);
I wrote a unit test to test the API call:
[TestClass]
public class ApiControllerTest
{
private ApiRepository repo = new ApiTestRepository();
[TestMethod]
public void Get()
{
// Arrange
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/driver/1");
var route = config.Routes.MapHttpRoute("Default", "api/{controller}/{id}");
ApiDriverController controller = new ApiDriverController(repo)
{
Request = request,
};
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
// Act
var Results = controller.Get(1);
// Assert
// ...
}
}
When I use a browser to call the API, it does have security. However, the API test doesn’t seem to require it.
Is there a reason the API test works when it shouldn’t? Is there a way I can test the security?
Login api unit test cases
This test method is responsible for unit testing the SignIn API. It covers the following 2 scenarios.
Setup authentication
Create new Startup.Auth.cs file which will contain the setup of authentication:
The Startup.Auth.cs contains the partials Startup class and initialises identity environment:
Setup project
The following article will add ASP.NET Core Identity to the sample project used by: Implementing SOLID REST API using ASP.NET Core.
In order to use OpenIddict, add the appropriate MyGet repositories to your NuGet sources. This can be done by adding a new NuGet.Config file at the root of your solution:
In order to use ASP.NET Core Identity and OpenIddict add the following packages to your project:
Unit testing authentication controller
Since application security is one of the critical aspects of an application, it’s our responsibility to unit test to make sure that it is defect-free and working as expected. Hence, we are gonna write some unit test cases for our AuthConroller in this Spring Boot Angular MySQL Maven Application.
Unit-тесты на c# — разработка на
А вы когда-нибудь задумывались о необходимости тестирования разрабатываемых приложений? Сегодня я попробую показать важность применения unit-тестов, которые призваны помочь в обнаружении ошибок на ранних этапах работы, что в последующем приводит к экономии ваших средств и ресурсов.
26 150просмотров
В процессе написания ПО у меня возникло понимание о целесообразности применения unit-тестов.
В моей практике появилось несколько проектов, в которых мне довелось писать unit-тесты, каждый из которых выполнял определенную роль — поиск ошибок в основных алгоритмах кода, нагрузочное тестирование и отладка бэкенда веб-приложения.
В каждой из поставленных задач unit-тесты оказались эффективны, позволив существенно сократить время работы и обеспечить своевременное обнаружение ошибок кода.
Согласно данным[1] исследований, цена ошибки в ходе разработки и поддержании ПО экспоненциально возрастает при несвоевременном их обнаружении.
На представленном рисунке видно, что при выявлении ошибки на этапе формирования требований мы получим экономию средств в соотношении 200:1 по сравнению с их обнаружением на этапе поддержки.
Среди всех тестов львиную долю занимают именно unit-тесты. В классическом понимании unit-тесты позволяют быстро и автоматически протестировать отдельные части ПО независимо от остальных.
Рассмотрим простой пример создания unit-тестов. Для этого создадим консольное приложение Calc, которое умеет делить и суммировать числа.
Добавляем класс, в котором будут производиться математические операции.
Так, в методе Div производится операция деления числа n1 на число n2. Если передаваемое число n2 будет равняться нулю, то такая ситуация приведет к исключению. Для этого знаменатель этой операции проверяется на равенство нулю.
Метод AddWithInc производит сложение двух передаваемых чисел и инкрементацию полученного результата суммирования на единицу.
На следующем шаге добавим в решение проект тестов.
Пустой проект unit-тестов:
Переименуем наш проект: «SimpleCalculatorTests». Добавляем ссылку на проект Calc.
В проекте Calc содержатся 2 метода, которые надо протестировать на корректность работы. Для этого создадим 3 теста, которые будут проверять операцию деления двух чисел, операцию деления на нуль и операцию сложения двух чисел и инкрементацию полученной суммы.
Добавляем в проект тест для проверки метода AddWithInc.
В тесте создаются 3 переменные — это аргументы, передаваемые в метод AddWithInc, и ожидаемый результат, возвращаемый этим методом. Результат выполнения метода будет записан в переменную result.
На следующем шаге происходит сравнение ожидаемого результата с реальным числом метода AddWithInc. При совпадении результата с ожидаемым числом, то есть числом 6, тест будет считаться положительным и пройденным. Если полученный результат будет отличаться от числа 6, то тест считается проваленным.
Следующим тестом мы будем проверять метод Div
Аналогичным образом создаются два аргумента и ожидаемый результат выполнения метода Div. Если результат деления 4/2 в методе равен 2, то тест считается пройдённым. В противном случае — не пройденным.
Следующий тест будет проверять операцию деления на нуль в методе Div.
Тест будет считаться пройденным в случае возникновения исключения DivideByZeroException — деление на нуль. В отличии от двух предыдущих тестов, в этом тесте нет оператора Assert. Здесь обработка ожидаемого результата производится с помощью атрибута «ExpectedException».
Если аргумент 2 равен нулю, то в методе Divвозникнет исключение — деление на нуль. В таком случае тест считается пройденным. В случае, когда аргумент 2 будет отличен от нуля, тест считается проваленным.
Для запуска теста необходимо открыть окно Test Explorer. Для этого нажмите Test -> Windows -> Test Explorer (Ctrl , T). В появившемся окне можно увидеть 3 добавленных теста:
Для запуска всех тестов нажмите Test -> Run -> All tests (Ctrl , A).
Если тесты выполнятся успешно, в окне Test Explorer отобразятся зеленые пиктограммы, обозначающие успешность выполнения.
В противном случае пиктограммы будут красными.
Unit-тесты имеют обширную, строго не регламентированную область применения — зачастую фантазия самого автора кода подсказывает решение нестандартных задач с помощью этого инструмента.
Случай написания тестов для бэкенда веб-приложения в моей практике является не совсем стандартным вариантом применения unit-тестов. В данной ситуации unit-тесты вызывали методы контроллера MVC-приложения, в то же время передавая тестовые данные в контроллеры.
Далее в режиме отладки шаг за шагом выполнялись все действия алгоритма. В этом случае применение тестов позволило произвести быструю отладку бэкенда веб-приложения.
Существуют случаи, когда модульные тесты применять нецелесообразно. Например, если вы веб-разработчик, который делает сайты, где мало логики. В таких случаях имеются только представления, как, например, для сайтов-визиток, рекламных сайтов, или, когда вам поставлена задача реализовать пилотный проект «на посмотреть, что получится». У вас ограниченные ресурсы и время. А ПО будет работать только один день — для показа руководству.
Сжатые сроки, малый бюджет, размытые цели или довольно несложные требования — случаи, в которых вы не получите пользы от написания тестов.
Для определения целесообразности использования unit-тестов можно воспользоваться следующим методом: возьмите лист бумаги и ручку и проведите оси X и Y. X — алгоритмическая сложность, а Y — количество зависимостей. Ваш код поделим на 4 группы.
- Простой код (без каких-либо зависимостей)
- Сложный код (содержащий много зависимостей)
- Сложный код (без каких-либо зависимостей)
- Не очень сложный код (но с зависимостями)
Первое — это случай, когда все просто и тестировать здесь ничего не нужно.
Второе — случай, когда код состоит только из плотно переплетенных в один клубок реализаций, перекрестно вызывающих друг друга. Тут неплохо было бы провести рефакторинг. Именно поэтому тесты писать в этом случае не стоит, так как код все равно будет переписан.
Третье — случай алгоритмов, бизнес-логики и т.п. Важный код, поэтому его нужно покрыть тестами.
Четвертый случай — код объединяет различные компоненты системы. Не менее важный случай.
Последние два случая — это ответственная логика. Особенно важно писать тесты для ПО, которые влияют на жизни людей, экономическую безопасность, государственную безопасность и т.п.
Подводя итог всего описанного выше хочется отметить, что тестирование делает код стабильным и предсказуемым. Поэтому код, покрытый тестами, гораздо проще масштабировать и поддерживать, т.к. появляется большая доля уверенности, что в случае добавления нового функционала нигде ничего не сломается. И что не менее важно — такой код легче рефакторить.
[1] Данные взяты из книги «Технология разработки программного обеспечения» автора Ларисы Геннадьевны Гагариной
Conclusion
That’s all folks. In this article, we have implemented unit test cases for our REST controller using Junit 5 and Mockito.
If you like learning from videos for testing/writing JUnit tests, then make sure to check out Philip’s Testing Spring Boot Applications Masterclass (if you buy through this link, I get a cut).
Thank you for reading.
Read Next:Test REST Controller with Spring Security using Mock Authentication or Disable Security in JUnit Tests